how to make a person
This is an exercise in making dictionaries with functions, the two important objects to know in Python.
Imagine I want to make a contact list or database of people. I can use a function to represent filling out information for a person, for example
First Name
Last Name (Surname)
Sex
Year of Birth
preview
I have these tests by the end of the chapter
1import datetime
2import random
3import src.person
4import unittest
5
6
7def pick_one(*choices):
8 return random.choice(choices)
9
10
11def get_random_name():
12 return pick_one(
13 'jane', 'joe', 'john', 'person',
14 'doe', 'smith', 'blow', 'public',
15 )
16
17
18def calculate_age(year_of_birth):
19 return (
20 datetime.datetime.now().year
21 - year_of_birth
22 )
23
24
25def get_random_year_of_birth():
26 this_year = datetime.datetime.now().year
27 return random.randint(
28 this_year-120, this_year
29 )
30
31
32class TestPerson(unittest.TestCase):
33
34 def test_factory_w_keyword_arguments(self):
35 year_of_birth = get_random_year_of_birth()
36
37 a_person = dict(
38 first_name=get_random_name(),
39 last_name=get_random_name(),
40 sex=pick_one('F', 'M'),
41 )
42
43 reality = src.person.factory(
44 **a_person,
45 year_of_birth=year_of_birth,
46 )
47 my_expectation = dict(
48 **a_person,
49 age=calculate_age(year_of_birth),
50 )
51 self.assertEqual(reality, my_expectation)
52
53 def test_factory_w_optional_arguments(self):
54 first_name = get_random_name()
55 year_of_birth = get_random_year_of_birth()
56
57 reality = src.person.factory(
58 first_name=first_name,
59 year_of_birth=year_of_birth,
60 )
61 my_expectation = dict(
62 first_name=first_name,
63 last_name='doe',
64 sex='M',
65 age=calculate_age(year_of_birth),
66 )
67 self.assertEqual(reality, my_expectation)
68
69 def test_factory_person_says_hello(self):
70 first_name = get_random_name()
71 last_name = get_random_name()
72 sex = pick_one('F', 'M')
73
74 year_of_birth = get_random_year_of_birth()
75 age = calculate_age(year_of_birth)
76
77 a_random_person = src.person.factory(
78 first_name=first_name,
79 last_name=last_name,
80 sex=sex,
81 year_of_birth=year_of_birth,
82 )
83
84 reality = src.person.say_hello(a_random_person)
85 my_expectation = (
86 f'Hi, my name is {first_name} {last_name}'
87 f' and I am {age}'
88 )
89 self.assertEqual(reality, my_expectation)
90
91
92# Exceptions seen
93# AssertionError
94# NameError
95# AttributeError
96# TypeError
97# SyntaxError
start the project
I name this project
personI open a terminal
I use uv to make a directory for the project and initialize it
uv init personthe terminal shows
Initialized project `person` at `.../pumping_python/person`then goes back to the command line.
I change directory to the project
cd personthe terminal shows I am in the
personfolder.../pumping_python/personI make a directory for the source code
mkdir srcthe terminal goes back to the command line.
I use the mv program to change the name of
main.pytoperson.pyand move it to thesrcfoldermv main.py src/person.pyMove-Item main.py src/person.pythe terminal goes back to the command line.
I make a directory for the tests
mkdir teststhe terminal goes back to the command line.
I make the
testsdirectory a Python packageDanger
use 2 underscores (__) before and after
initfor__init__.pynot_init_.pytouch tests/__init__.pyNew-Item tests/__init__.pythe terminal goes back to the command line.
I make a Python file for the tests in the
testsdirectorytouch tests/test_person.pyNew-Item tests/test_person.pythe terminal goes back to the command line.
I open
test_person.pyin the editor of the Integrated Development Environment (IDE)Tip
I can open a file from the terminal in the Integrated Development Environment (IDE) with the name of the program and the name of the file. That means if I type this in the terminal
code tests/test_person.pyVisual Studio Code opens
test_person.pyin the editorI add the first failing test to
test_person.py1import unittest 2 3 4class TestPerson(unittest.TestCase): 5 6 def test_failure(self): 7 self.assertFalse(True)I go back to the terminal to make a requirements file for the Python packages I need
echo "pytest" > requirements.txtthe terminal goes back to the command line.
I add pytest-watcher to the requirements file
echo "pytest-watcher" >> requirements.txtthe terminal goes back to the command line.
I install the Python packages that I wrote in the requirements file
uv add --requirement requirements.txtthe terminal shows that it installed the Python packages
I add the new files and folders to git for tracking
git add .the terminal goes back to the command line.
I add a git commit message
git commit -am 'setup project'the terminal shows
[main (root-commit) a0b12c3] setup project 9 files changed, 148 insertions(+) create mode 100644 .gitignore create mode 100644 .python-version create mode 100644 README.md create mode 100644 pyproject.toml create mode 100644 requirements.txt create mode 100644 src/person.py create mode 100644 tests/__init__.py create mode 100644 tests/test_person.py create mode 100644 uv.lockthen goes back to the command line.
I use pytest-watcher to run the tests automatically
uv run pytest-watcher . --nowthe terminal is my friend, and shows AssertionError
================================ FAILURES ================================ ______________________ TestPerson.test_failure ________________________ self = <tests.test_person.TestPerson testMethod=test_failure> def test_failure(self): > self.assertFalse(True) E AssertionError: True is not false tests/test_person.py:7: AssertionError ======================== short test summary info ========================= FAILED tests/test_person.py::TestPerson::test_failure - AssertionError: True is not false =========================== 1 failed in X.YZs ============================if the terminal does not show the same error, then check
if your
tests/__init__.pyhas two underscores (__) before and afterinitfor__init__.pynot_init_.pyif you ran
echo "pytest-watcher" >> requirements.txt, to addpytest-watcherto the requirements file
fix those errors and try to run
uv run pytest-watcher . --nowagainI add AssertionError to the list of Exceptions seen in
test_person.pyin the editor4class TestPerson(unittest.TestCase): 5 6 def test_failure(self): 7 self.assertFalse(True) 8 9 10# Exceptions seen 11# AssertionErrorthen I change True to False in the assertion
7 self.assertFalse(False)the test passes.
test_factory_w_keyword_arguments
RED: make it fail
I change
test_failureto test_factory_w_keyword_arguments4class TestPerson(unittest.TestCase): 5 6 def test_factory_w_keyword_arguments(self): 7 reality = src.person.factory() 8 my_expectation = None 9 self.assertEqual(reality, my_expectation) 10 11 12# Exceptions seen 13# AssertionErrorthe terminal is my friend, and shows NameError
NameError: name 'src' is not definedbecause there is no definition for
srcintest_person.pyI add NameError to the list of Exceptions seen
12# Exceptions seen 13# AssertionError 14# NameError
GREEN: make it pass
I add an import statement for the
personmodule at the top oftest_person.py1import src.person 2import unittestimport src.personbrings in an object (everything in Python is an object) that represents theperson.pymodule from thesrcfolder so I can use it intest_person.pythe terminal is my friend, and shows AttributeError
AttributeError: module 'src.person' has no attribute 'factory'because there is nothing in
person.pywith that name
I add AttributeError to the list of Exceptions seen
13# Exceptions seen 14# AssertionError 15# NameError 16# AttributeErrorI use the Explorer to open
person.pyfrom thesrcfolder in the editorI delete the text, then add a function to
person.py1def factory(): 2 return Nonethe test passes.
REFACTOR: make it better
I want the function to take a keyword argument called
first_name. I add it to the test intest_person.py7 def test_factory_w_keyword_arguments(self): 8 reality = src.person.factory( 9 first_name='first_name', 10 ) 11 my_expectation = None 12 self.assertEqual(reality, my_expectation) 13 14 15# Exceptions seenthe terminal is my friend, and shows TypeError
TypeError: factory() got an unexpected keyword argument 'first_name'because the function definition for
src.person.factorydoes not allow calling it with inputs (the parentheses are empty) and the test sends'first_name'as input.I add TypeError to the list of Exceptions seen
15# Exceptions seen 16# AssertionError 17# NameError 18# AttributeError 19# TypeErrorI add
first_nameas an input parameter to the function inperson.py1def factory(first_name): 2 return Nonethe test passes.
I want the function to take a keyword argument called
last_name. I add it to the test intest_person.py7 def test_factory_w_keyword_arguments(self): 8 reality = src.person.factory( 9 first_name='first_name', 10 last_name='last_name', 11 ) 12 my_expectation = None 13 self.assertEqual(reality, my_expectation) 14 15 16# Exceptions seenthe terminal is my friend, and shows TypeError
TypeError: factory() got an unexpected keyword argument 'last_name'. Did you mean 'first_name'?because the test called the factory function with a keyword argument (
last_name) that is not in the function definitionI add
last_nameto the function definition inperson.py1def factory(first_name, last_name): 2 return Nonethe test passes.
I want the function to take a keyword argument called
sex. I add it to the test intest_person.py7 def test_factory_w_keyword_arguments(self): 8 reality = src.person.factory( 9 first_name='first_name', 10 last_name='last_name', 11 sex='M', 12 ) 13 my_expectation = None 14 self.assertEqual(reality, my_expectation) 15 16 17# Exceptions seenthe terminal is my friend, and shows TypeError
TypeError: factory() got an unexpected keyword argument 'sex'because the test called the factory function with a keyword argument (
sex) that is not in the function definitionI add
sexas an input parameter to the factory function inperson.py1def factory( 2 first_name, last_name, 3 sex, 4 ): 5 return Nonethe test passes.
I want the function to take a keyword argument for
year_of_birth. I add it to the test intest_person.py7 def test_factory_w_keyword_arguments(self): 8 reality = src.person.factory( 9 first_name='first_name', 10 last_name='last_name', 11 sex='M', 12 year_of_birth=2000, 13 ) 14 my_expectation = None 15 self.assertEqual(reality, my_expectation) 16 17 18# Exceptions seenthe terminal is my friend, and shows TypeError
TypeError: factory() got an unexpected keyword argument 'year_of_birth'because the test called the factory function with a keyword argument (
year_of_birth) that is not in the function definitionI add the name to the function definition in
person.py1def factory( 2 first_name, last_name, 3 sex, year_of_birth, 4 ): 5 return Nonethe test passes.
I want the factory function to return a dictionary (any key-value pairs in curly braces
{ }separated by a comma) as output when it is called. I changemy_expectationintest_person.py7 def test_factory_w_keyword_arguments(self): 8 reality = src.person.factory( 9 first_name='first_name', 10 last_name='last_name', 11 sex='M', 12 year_of_birth=2000, 13 ) 14 my_expectation = dict() 15 self.assertEqual(reality, my_expectation) 16 17 18# Exceptions seenthe terminal is my friend, and shows AssertionError
AssertionError: None != {}because the function returns None and the assertion expects
{}I change None to a dictionary in the return statement in
person.py1def factory( 2 first_name, last_name, 3 sex, year_of_birth, 4 ): 5 return {}the test passes because
{}anddict()are two ways to make the empty dictionary.
I add a key called
first_nameto the dictionary formy_expectation, with the same value as what is given in the call to the factory function intest_person.py7 def test_factory_w_keyword_arguments(self): 8 reality = src.person.factory( 9 first_name='first_name', 10 last_name='last_name', 11 sex='M', 12 year_of_birth=2000, 13 ) 14 my_expectation = dict( 15 first_name='first_name', 16 ) 17 self.assertEqual(reality, my_expectation) 18 19 20# Exceptions seenthe terminal is my friend, and shows AssertionError
AssertionError: {} != {'first_name': 'first_name'}because the function returns the empty dictionary and the assertion expects one with
first_nameas the keyI change the return statement to give the test what it wants, in
person.py1def factory( 2 first_name, last_name, 3 sex, year_of_birth, 4 ): 5 return {'first_name': 'first_name'}the test passes.
I change the value of
first_nameto'jane'to use a real name forrealityandmy_expectation, intest_person.py7 def test_factory_w_keyword_arguments(self): 8 reality = src.person.factory( 9 # first_name='first_name', 10 first_name='jane', 11 last_name='last_name', 12 sex='M', 13 year_of_birth=2000, 14 ) 15 my_expectation = dict( 16 # first_name='first_name', 17 first_name='jane', 18 ) 19 self.assertEqual(reality, my_expectation) 20 21 22# Exceptions seenthe terminal is my friend, and shows AssertionError
AssertionError: {'first_name': 'first_name'} != {'first_name': 'jane'}because I changed the value for
first_nameinmy_expectationI change the value for the
first_namekey inperson.py1def factory( 2 first_name, last_name, 3 sex, year_of_birth, 4 ): 5 # return {'first_name': 'first_name'} 6 return {'first_name': 'jane'}the test passes.
I typed the value for
first_nametwo times in the test, which means I have to make a change in two places every time I want a different value for it. I add a variable to use to remove the repetition of'jane'fromtest_person.py7 def test_factory_w_keyword_arguments(self): 8 first_name = 'jane' 9 10 reality = src.person.factory(I use the variable to remove repetition of
'jane'7 def test_factory_w_keyword_arguments(self): 8 first_name = 'jane' 9 10 reality = src.person.factory( 11 # first_name='first_name', 12 # first_name='jane', 13 first_name=first_name, 14 last_name='last_name', 15 sex='M', 16 year_of_birth=2000, 17 ) 18 my_expectation = dict( 19 # first_name='first_name', 20 # first_name='jane', 21 first_name=first_name, 22 ) 23 self.assertEqual(reality, my_expectation) 24 25 26# Exceptions seenthe test is still green. I now only need to change the value of
first_namein one place in the test.
I add a key called
last_nameto the dictionary, with the same value as what is given in the call to the factory function intest_person.py7 def test_factory_w_keyword_arguments(self): 8 first_name = 'jane' 9 10 reality = src.person.factory( 11 # first_name='first_name', 12 # first_name='jane', 13 first_name=first_name, 14 last_name='last_name', 15 sex='M', 16 year_of_birth=2000, 17 ) 18 my_expectation = dict( 19 # first_name='first_name', 20 # first_name='jane', 21 first_name=first_name, 22 last_name='last_name', 23 ) 24 self.assertEqual(reality, my_expectation) 25 26 27# Exceptions seenthe terminal is my friend, and shows AssertionError
AssertionError: {'first_name': 'jane'} != {'first_name': 'jane', 'last_name': 'last_name'}I change the return statement in
person.py1def factory( 2 first_name, last_name, 3 sex, year_of_birth, 4 ): 5 # return {'first_name': 'first_name'} 6 # return {'first_name': 'jane'} 7 return { 8 'first_name': 'jane', 9 'last_name': 'last_name', 10 }the test passes.
I change the value of
last_nameto'doe'to use a real name forrealityandmy_expectation, intest_person.py7 def test_factory_w_keyword_arguments(self): 8 first_name = 'jane' 9 10 reality = src.person.factory( 11 # first_name='first_name', 12 # first_name='jane', 13 first_name=first_name, 14 # last_name='last_name', 15 last_name='doe', 16 sex='M', 17 year_of_birth=2000, 18 ) 19 my_expectation = dict( 20 # first_name='first_name', 21 # first_name='jane', 22 first_name=first_name, 23 # last_name='last_name', 24 last_name='doe', 25 ) 26 self.assertEqual(reality, my_expectation) 27 28 29# Exceptions seenthe terminal is my friend, and shows AssertionError
AssertionError: {'first_name': 'jane', 'last_name': 'last_name'} != {'first_name': 'jane', 'last_name': 'doe'}because I changed the value for
last_nameinmy_expectationI change the value for the
last_namekey inperson.py1def factory( 2 first_name, last_name, 3 sex, year_of_birth, 4 ): 5 # return {'first_name': 'first_name'} 6 # return {'first_name': 'jane'} 7 return { 8 'first_name': 'jane', 9 # 'last_name': 'last_name', 10 'last_name': 'doe', 11 }the test passes.
I typed the value for
last_nametwo times in the test, which means I have to make a change in two places every time I want a different value for it. I add a variable to use to remove the repetition of'doe'fromtest_person.py7 def test_factory_w_keyword_arguments(self): 8 first_name = 'jane' 9 last_name = 'doe' 10 11 reality = src.person.factory(I use the variable to remove repetition of
'doe'7 def test_factory_w_keyword_arguments(self): 8 first_name = 'jane' 9 last_name = 'doe' 10 11 reality = src.person.factory( 12 # first_name='first_name', 13 # first_name='jane', 14 first_name=first_name, 15 # last_name='last_name', 16 # last_name='doe', 17 last_name=last_name, 18 sex='M', 19 year_of_birth=2000, 20 ) 21 my_expectation = dict( 22 # first_name='first_name', 23 # first_name='jane', 24 first_name=first_name, 25 # last_name='last_name', 26 # last_name='doe', 27 last_name=last_name, 28 ) 29 self.assertEqual(reality, my_expectation) 30 31 32# Exceptions seenthe test is still green. I now only need to change the value of
first_namein one place in the test.
I add a key called
sexto the dictionary, with the same value as what is given in the call to the factory function intest_person.py7 def test_factory_w_keyword_arguments(self): 8 first_name = 'jane' 9 last_name = 'doe' 10 11 reality = src.person.factory( 12 # first_name='first_name', 13 # first_name='jane', 14 first_name=first_name, 15 # last_name='last_name', 16 # last_name='doe', 17 last_name=last_name, 18 sex='M', 19 year_of_birth=2000, 20 ) 21 my_expectation = dict( 22 # first_name='first_name', 23 # first_name='jane', 24 first_name=first_name, 25 # last_name='last_name', 26 # last_name='doe', 27 last_name=last_name, 28 sex='M', 29 ) 30 self.assertEqual(reality, my_expectation) 31 32 33# Exceptions seenthe terminal is my friend, and shows AssertionError
AssertionError: {'first_name': 'jane', 'last_name': 'doe'} != {'first_name': 'jane', 'last_name': 'doe', 'sex': 'M'}I add a new key to the return statement in
person.py1def factory( 2 first_name, last_name, 3 sex, year_of_birth, 4 ): 5 # return {'first_name': 'first_name'} 6 # return {'first_name': 'jane'} 7 return { 8 'first_name': 'jane', 9 # 'last_name': 'last_name', 10 'last_name': 'doe', 11 'sex': 'M', 12 }the test passes.
I change the value of
sexto'F'forrealityandmy_expectation, intest_person.py7 def test_factory_w_keyword_arguments(self): 8 first_name = 'jane' 9 last_name = 'doe' 10 11 reality = src.person.factory( 12 # first_name='first_name', 13 # first_name='jane', 14 first_name=first_name, 15 # last_name='last_name', 16 # last_name='doe', 17 last_name=last_name, 18 # sex='M', 19 sex='F', 20 year_of_birth=2000, 21 ) 22 my_expectation = dict( 23 # first_name='first_name', 24 # first_name='jane', 25 first_name=first_name, 26 # last_name='last_name', 27 # last_name='doe', 28 last_name=last_name, 29 # sex='M', 30 sex='F', 31 ) 32 self.assertEqual(reality, my_expectation) 33 34 35# Exceptions seenthe terminal is my friend, and shows AssertionError
AssertionError: {'first_name': 'jane', 'last_name': 'doe', 'sex': 'M'} != {'first_name': 'jane', 'last_name': 'doe', 'sex': 'F'}because I changed the value for
sexinmy_expectationI change the value for the
sexkey inperson.py1def factory( 2 first_name, last_name, 3 sex, year_of_birth, 4 ): 5 # return {'first_name': 'first_name'} 6 # return {'first_name': 'jane'} 7 return { 8 'first_name': 'jane', 9 # 'last_name': 'last_name', 10 'last_name': 'doe', 11 # 'sex': 'M', 12 'sex': 'F', 13 }the test passes.
I typed the value for
sextwo times in the test, which means I have to make a change in two places every time I want a different value for it. I add a variable to use to remove the repetition of'F'fromtest_person.py7 def test_factory_w_keyword_arguments(self): 8 first_name = 'jane' 9 last_name = 'doe' 10 sex = 'F' 11 12 reality = src.person.factory(I use the variable to remove repetition of
'F'7 def test_factory_w_keyword_arguments(self): 8 first_name = 'jane' 9 last_name = 'doe' 10 sex = 'F' 11 12 reality = src.person.factory( 13 # first_name='first_name', 14 # first_name='jane', 15 first_name=first_name, 16 # last_name='last_name', 17 # last_name='doe', 18 last_name=last_name, 19 # sex='M', 20 # sex='F', 21 sex=sex, 22 year_of_birth=2000, 23 ) 24 my_expectation = dict( 25 # first_name='first_name', 26 # first_name='jane', 27 first_name=first_name, 28 # last_name='last_name', 29 # last_name='doe', 30 last_name=last_name, 31 # sex='M', 32 # sex='F', 33 sex=sex, 34 ) 35 self.assertEqual(reality, my_expectation) 36 37 38# Exceptions seenthe test is still green. I now only need to change the value of
sexin one place in the test.
I want the factory function to return the age of the person it makes. I add a key to
my_expectation7 def test_factory_w_keyword_arguments(self): 8 first_name = 'jane' 9 last_name = 'doe' 10 sex = 'F' 11 12 reality = src.person.factory( 13 # first_name='first_name', 14 # first_name='jane', 15 first_name=first_name, 16 # last_name='last_name', 17 # last_name='doe', 18 last_name=last_name, 19 # sex='M', 20 # sex='F', 21 sex=sex, 22 year_of_birth=2000, 23 ) 24 my_expectation = dict( 25 # first_name='first_name', 26 # first_name='jane', 27 first_name=first_name, 28 # last_name='last_name', 29 # last_name='doe', 30 last_name=last_name, 31 # sex='M', 32 # sex='F', 33 sex=sex, 34 age=2026-2000, 35 ) 36 self.assertEqual(reality, my_expectation) 37 38 39# Exceptions seenthe terminal is my friend, and shows AssertionError
AssertionError: {'first_name': 'jane', 'last_name': 'doe', 'sex': 'F'} != {'first_name': 'jane', 'last_name': 'doe', 'sex': 'F', 'age': 26}I add a new key to the return statement in
person.py1def factory( 2 first_name, last_name, 3 sex, year_of_birth, 4 ): 5 # return {'first_name': 'first_name'} 6 # return {'first_name': 'jane'} 7 return { 8 'first_name': 'jane', 9 # 'last_name': 'last_name', 10 'last_name': 'doe', 11 # 'sex': 'M', 12 'sex': 'F', 13 'age': 26, 14 }the test passes.
I change
2000to1996forrealityandmy_expectation, intest_person.py7 def test_factory_w_keyword_arguments(self): 8 first_name = 'jane' 9 last_name = 'doe' 10 sex = 'F' 11 12 reality = src.person.factory( 13 # first_name='first_name', 14 # first_name='jane', 15 first_name=first_name, 16 # last_name='last_name', 17 # last_name='doe', 18 last_name=last_name, 19 # sex='M', 20 # sex='F', 21 sex=sex, 22 # year_of_birth=2000, 23 year_of_birth=1996, 24 ) 25 my_expectation = dict( 26 # first_name='first_name', 27 # first_name='jane', 28 first_name=first_name, 29 # last_name='last_name', 30 # last_name='doe', 31 last_name=last_name, 32 # sex='M', 33 # sex='F', 34 sex=sex, 35 # age=2026-2000, 36 age=2026-1996, 37 ) 38 self.assertEqual(reality, my_expectation) 39 40 41# Exceptions seenthe terminal is my friend, and shows AssertionError
AssertionError: {'first_name': 'jane', 'last_name': 'doe', 'sex': 'F', 'age': 26} != {'first_name': 'jane', 'last_name': 'doe', 'sex': 'F', 'age': 30}because I changed
2000inmy_expectationI change the value for the
agekey inperson.py1def factory( 2 first_name, last_name, 3 sex, year_of_birth, 4 ): 5 # return {'first_name': 'first_name'} 6 # return {'first_name': 'jane'} 7 return { 8 'first_name': 'jane', 9 # 'last_name': 'last_name', 10 'last_name': 'doe', 11 # 'sex': 'M', 12 'sex': 'F', 13 # 'age': 26, 14 'age': 30, 15 }the test passes.
I remove the commented lines
1def factory( 2 first_name, last_name, 3 sex, year_of_birth, 4 ): 5 return { 6 'first_name': 'jane', 7 'last_name': 'doe', 8 'sex': 'F', 9 'age': 30, 10 }I typed the year of birth two times in the test, which means I have to make a change in two places every time I want a different value for it. I add a variable to use to remove the repetition of the year of birth from
test_person.py7 def test_factory_w_keyword_arguments(self): 8 first_name = 'jane' 9 last_name = 'doe' 10 sex = 'F' 11 year_of_birth = 1996 12 13 reality = src.person.factory(I use the variable to remove repetition of the year of birth
7 def test_factory_w_keyword_arguments(self): 8 first_name = 'jane' 9 last_name = 'doe' 10 sex = 'F' 11 year_of_birth = 1996 12 13 reality = src.person.factory( 14 # first_name='first_name', 15 # first_name='jane', 16 first_name=first_name, 17 # last_name='last_name', 18 # last_name='doe', 19 last_name=last_name, 20 # sex='M', 21 # sex='F', 22 sex=sex, 23 # year_of_birth=2000, 24 # year_of_birth=1996, 25 year_of_birth=year_of_birth, 26 ) 27 my_expectation = dict( 28 # first_name='first_name', 29 # first_name='jane', 30 first_name=first_name, 31 # last_name='last_name', 32 # last_name='doe', 33 last_name=last_name, 34 # sex='M', 35 # sex='F', 36 sex=sex, 37 # age=2026-2000, 38 # age=2026-1996, 39 age=2026-year_of_birth, 40 ) 41 self.assertEqual(reality, my_expectation) 42 43 44# Exceptions seenthe test is still green.
I remove the commented lines
7 def test_factory_w_keyword_arguments(self): 8 first_name = 'jane' 9 last_name = 'doe' 10 sex = 'F' 11 year_of_birth = 1996 12 13 reality = src.person.factory( 14 first_name=first_name, 15 last_name=last_name, 16 sex=sex, 17 year_of_birth=year_of_birth, 18 ) 19 my_expectation = dict( 20 first_name=first_name, 21 last_name=last_name, 22 sex=sex, 23 age=2026-year_of_birth, 24 ) 25 self.assertEqual(reality, my_expectation) 26 27 28# Exceptions seenI now only need to change the value of
sexin one place in the test, though I have a problem with the calculation for the age, it will be wrong if this program is run after 2026.I open a new terminal then change directories to
personcd personI add a git commit message in the other terminal
git commit -am 'add test_factory_w_keyword_arguments'the terminal shows a summary of the changes then goes back to the command line.
test factory with random year_of_birth
I want the value of the age to be a calculation based on the current year so that it will always be correct (at least most of the time). I can do that with the datetime module from The Python Standard Library which is used for dates and times
I go back to the terminal that is running the tests
I add an import statement for the datetime module at the top of
test_person.py1import datetime 2import src.person 3import unittestimport datetimebrings in an object (everything in Python is an object) that represents the datetime module so I can use it intest_person.pyI change
2026inmy_expectationto use a method from the datetime module8 def test_factory_w_keyword_arguments(self): 9 first_name = 'jane' 10 last_name = 'doe' 11 sex = 'F' 12 year_of_birth = 1996 13 14 reality = src.person.factory( 15 first_name=first_name, 16 last_name=last_name, 17 sex=sex, 18 year_of_birth=year_of_birth, 19 ) 20 my_expectation = dict( 21 first_name=first_name, 22 last_name=last_name, 23 sex=sex, 24 # age=2026-year_of_birth, 25 age=( 26 datetime.datetime.now().year 27 -year_of_birth 28 ), 29 ) 30 self.assertEqual(reality, my_expectation) 31 32 33# Exceptions seenI can also use the today method to get the same value
datetime.datetime.today().yearthe test is still green.
datetimeis the datetime moduledatetime.datetimeis a call to the datetime object of the datetime module. Wait a minute, that is the same name again. Do I have to remember all this?datetime.datetime.now()is a call to the now method of the datetime.datetime object from the datetime module, it returns a datetime.datetime object. Oh boy!datetime.datetime.today()is a call to the today method of the datetime.datetime object from the datetime module, it returns a datetime.datetime objectdatetime.datetime.now().yearordatetime.datetime.today().yearasks for the value of theyearclass attribute of the datetime.datetime object returned by the now method or today method of the datetime.datetime object from the datetime module
that was a lot of words, they become clearer in the chapters on classes
I want to use random values in the test to make sure the factory function can handle different values and always calculates the right age
RED: make it fail
I add an import statement for the random module at the top of
test_person.py1import datetime 2import random 3import src.person 4import unittestrandom is a module from The Python Standard Library that is used to make fake random numbers
import randombrings in an object (everything in Python is an object) that represents the random module so I can use it intest_person.pyI like to sort my import statements alphabetically
I use a random integer (a whole number without decimals) for the
year_of_birthvariable9 def test_factory_w_keyword_arguments(self): 10 first_name = 'jane' 11 last_name = 'doe' 12 sex = 'F' 13 # year_of_birth = 1996 14 year_of_birth = random.randint( 15 datetime.datetime.now().year-120, 16 datetime.datetime.now().year 17 ) 18 19 reality = src.person.factory( 20 first_name=first_name, 21 last_name=last_name, 22 sex=sex, 23 year_of_birth=year_of_birth, 24 )randomis the random modulerandom.randint()is a call to the randint method from the random module. Okay, this one does not use the same name againdatetime.datetime.now().yeargives me this yeardatetime.datetime.now().year-120gives me this year minus120random.randint(datetime.datetime.now().year-120, datetime.datetime.now().year)gives me a random number from 120 years ago, up to and including the current yearthe terminal is my friend, and shows AssertionError
AssertionError: {'first_name': 'jane', 'last_name': 'doe', 'sex': 'F', 'age': 30} != {'first_name': 'jane', 'last_name': 'doe', 'sex': 'F', 'age': X}where
Xis a random age
GREEN: make it pass
I add a calculation for the age with the today method to the return statement in
person.py1def factory( 2 first_name, last_name, 3 sex, year_of_birth, 4 ): 5 return { 6 'first_name': 'jane', 7 'last_name': 'doe', 8 'sex': 'F', 9 # 'age': 30, 10 'age': ( 11 datetime.datetime.today().year 12 -year_of_birth 13 ), 14 }the terminal is my friend, and shows NameError
NameError: name 'datetime' is not defined. Did you forget to import 'datetime'I add an import statement for the datetime module at the top of
person.py1import datetime 2 3 4def factory( 5 first_name, last_name, 6 sex, year_of_birth, 7 ):import datetimebrings in an object (everything in Python is an object) that represents the datetime module so I can use it inperson.pythe test passes.
REFACTOR: make it better
I add a variable to use to remove repetition of
datetime.datetime.now().yearfrom the test intest_person.py9 def test_factory_w_keyword_arguments(self): 10 first_name = 'jane' 11 last_name = 'doe' 12 sex = 'F' 13 # year_of_birth = 1996 14 this_year = datetime.datetime.now().year 15 year_of_birth = random.randint( 16 datetime.datetime.now().year-120, 17 datetime.datetime.now().year 18 )I use the variable to remove repetition of
datetime.datetime.now().yearfrom the test9 def test_factory_w_keyword_arguments(self): 10 first_name = 'jane' 11 last_name = 'doe' 12 sex = 'F' 13 # year_of_birth = 1996 14 this_year = datetime.datetime.now().year 15 year_of_birth = random.randint( 16 # datetime.datetime.now().year-120, 17 # datetime.datetime.now().year 18 this_year-120, this_year 19 ) 20 21 reality = src.person.factory( 22 first_name=first_name, 23 last_name=last_name, 24 sex=sex, 25 year_of_birth=year_of_birth, 26 ) 27 my_expectation = dict( 28 first_name=first_name, 29 last_name=last_name, 30 sex=sex, 31 # age=2026-year_of_birth, 32 # age=( 33 # datetime.datetime.now().year 34 # -year_of_birth 35 # ), 36 age=this_year-year_of_birth, 37 ) 38 self.assertEqual(reality, my_expectation) 39 40 41# Exceptions seenI add a git commit message in the other terminal
git commit -am 'use random values for age'the terminal shows a summary of the changes then goes back to the command line.
test factory with random sex
RED: make it fail
I go back to the terminal that is running the tests
I add randomness to the
sexvariable intest_person.py9 def test_factory_w_keyword_arguments(self): 10 first_name = 'jane' 11 last_name = 'doe' 12 # sex = 'F' 13 sex = random.choice(('F', 'M')) 14 # year_of_birth = 1996 15 this_year = datetime.datetime.now().year 16 year_of_birth = random.randint( 17 # datetime.datetime.now().year-120, 18 # datetime.datetime.now().year 19 this_year-120, this_year 20 )randomis the random modulerandom.choice()is a call to the random.choice method from the random module, it returns a random value from the iterable it is given in parentheses('F', 'M')is a tuple (an iterable) with values for the random.choice method to pick from randomlyrandom.choice(('F', 'M'))randomly gives meForMevery time the test runs
I use ctrl+s (Windows/Linux) or command+s (MacOS) to run the test a few times and it passes if
sexis randomly'F'.If
sexis randomly'M', the terminal is my friend, and shows AssertionErrorAssertionError: {'first_name': 'jane', 'last_name': 'doe', 'sex': 'F', 'age': X} != {'first_name': 'jane', 'last_name': 'doe', 'sex': 'M', 'age': X}where
Xis the random age
GREEN: make it pass
I add the
sexinput parameter instead of a value that does not change to the return statement inperson.py4def factory( 5 first_name, last_name, 6 sex, year_of_birth, 7 ): 8 return { 9 'first_name': 'jane', 10 'last_name': 'doe', 11 # 'sex': 'F', 12 'sex': sex, 13 # 'age': 30, 14 'age': ( 15 datetime.datetime.today().year 16 -year_of_birth 17 ), 18 }I use ctrl+s (Windows/Linux) or command+s (MacOS) to run the test a few times and it passes with no more random failures
I add a git commit message in the other terminal
git commit -am 'use random values for sex'the terminal shows a summary of the changes then goes back to the command line.
test factory with random last name
RED: make it fail
I go back to the terminal that is running the tests
I use random.choice with the
last_namevariable intest_person.py9 def test_factory_w_keyword_arguments(self): 10 first_name = 'jane' 11 # last_name = 'doe' 12 last_name = random.choice(( 13 'doe', 'smith', 'blow', 'public', 14 )) 15 # sex = 'F' 16 sex = random.choice(('F', 'M')) 17 # year_of_birth = 1996 18 this_year = datetime.datetime.now().year 19 year_of_birth = random.randint( 20 # datetime.datetime.now().year-120, 21 # datetime.datetime.now().year 22 this_year-120, this_year 23 )I use ctrl+s (Windows/Linux) or command+s (MacOS) to run the test a few times and it passes if
last_nameis'doe'.If
last_nameis NOTdoe, the terminal is my friend, and shows AssertionErrorAssertionError: {'first_name': 'jane', 'last_name': Z, 'sex': Y, 'age': X} != {'first_name': 'jane', 'last_name': A, 'sex': Y, 'age': X}where
ZandAare the different random last names,Xis the random age, andYis the random sex.
GREEN: make it pass
I use the
last_nameinput parameter as the value for the'last_name'key in the return statement inperson.py4def factory( 5 first_name, last_name, 6 sex, year_of_birth, 7 ): 8 return { 9 'first_name': 'jane', 10 # 'last_name': 'doe', 11 'last_name': last_name, 12 # 'sex': 'F', 13 'sex': sex, 14 # 'age': 30, 15 'age': ( 16 datetime.datetime.today().year 17 -year_of_birth 18 ), 19 }I use ctrl+s (Windows/Linux) or command+s (MacOS) to run the test a few times and it passes with no more random failures
I add a git commit message in the other terminal
git commit -am 'use random values for last_name'the terminal shows a summary of the changes then goes back to the command line.
test factory with random first name
RED: make it fail
I go back to the terminal that is running the tests
I add randomness to the
first_namevariable intest_person.py9 def test_factory_w_keyword_arguments(self): 10 # first_name = 'jane' 11 first_name = random.choice(( 12 'jane', 'joe', 'john', 'person', 13 )) 14 # last_name = 'doe' 15 last_name = random.choice(( 16 'doe', 'smith', 'blow', 'public', 17 )) 18 # sex = 'F' 19 sex = random.choice(('F', 'M')) 20 # year_of_birth = 1996 21 this_year = datetime.datetime.now().year 22 year_of_birth = random.randint( 23 # datetime.datetime.now().year-120, 24 # datetime.datetime.now().year 25 this_year-120, this_year 26 )I use ctrl+s (Windows/Linux) or command+s (MacOS) to run the test a few times and it passes if
first_nameis'jane'.If
first_nameis not'jane'the terminal is my friend, and shows AssertionErrorAssertionError: {'first_name': A, 'last_name': Z, 'sex': Y, 'age': X} != {'first_name': B, 'last_name': Z, 'sex': Y, 'age': X}where
AandBare the different random first names,Xis the random age,Yis the random sex, andZis the random last nameI add the
first_nameinput parameter to the return statement inperson.py4def factory( 5 first_name, last_name, 6 sex, year_of_birth, 7 ): 8 return { 9 # 'first_name': 'jane', 10 'first_name': first_name, 11 # 'last_name': 'doe', 12 'last_name': last_name, 13 # 'sex': 'F', 14 'sex': sex, 15 # 'age': 30, 16 'age': ( 17 datetime.datetime.today().year 18 -year_of_birth 19 ), 20 }I use ctrl+s (Windows/Linux) or command+s (MacOS) to run the test a few times and it passes with no more random failures
I remove the commented lines
4def factory( 5 first_name, last_name, 6 sex, year_of_birth, 7 ): 8 return { 9 'first_name': first_name, 10 'last_name': last_name, 11 'sex': sex, 12 'age': ( 13 datetime.datetime.today().year 14 -year_of_birth 15 ), 16 }I add a git commit message in the other terminal
git commit -am 'use random values for first_name'the terminal shows a summary of the changes then goes back to the command line.
extract pick_one function
I go back to the terminal that is running the tests
I add a function for the calls to the random.choice method with a starred expression like I did in test_w_unknown_arguments, to use to remove repetition of
random.choicefrom the test intest_person.py1import datetime 2import random 3import src.person 4import unittest 5 6 7def pick_one(*choices): 8 return random.choice(choices) 9 10 11class TestPerson(unittest.TestCase): 12 13 def test_factory_w_keyword_arguments(self):I use the new function to remove repetition of
random.choicefrom the test13 def test_factory_w_keyword_arguments(self): 14 # first_name = 'jane' 15 # first_name = random.choice(( 16 # 'jane', 'joe', 'john', 'person', 17 # )) 18 first_name = pick_one( 19 'jane', 'joe', 'john', 'person', 20 ) 21 # last_name = 'doe' 22 # last_name = random.choice(( 23 # 'doe', 'smith', 'blow', 'public', 24 # )) 25 last_name = pick_one( 26 'doe', 'smith', 'blow', 'public', 27 ) 28 # sex = 'F' 29 # sex = random.choice(('F', 'M')) 30 sex = pick_one('F', 'M') 31 # year_of_birth = 1996 32 this_year = datetime.datetime.now().year 33 year_of_birth = random.randint( 34 # datetime.datetime.now().year-120, 35 # datetime.datetime.now().year 36 this_year-120, this_year 37 )the test is still green.
I add a git commit message in the other terminal
git commit -am 'extract pick_one function'the terminal shows a summary of the changes then goes back to the command line.
test factory with a dictionary
The difference between the call to the factory function and the expected dictionary in the test is, one has a year of birth and the other does a calculation with the year of birth. The other things are the same.
RED: make it fail
I go back to the terminal that is running the tests
I add a dictionary to use to remove the repeating parts
13 def test_factory_w_keyword_arguments(self): 14 # first_name = 'jane' 15 # first_name = random.choice(( 16 # 'jane', 'joe', 'john', 'person', 17 # )) 18 first_name = pick_one( 19 'jane', 'joe', 'john', 'person', 20 ) 21 # last_name = 'doe' 22 # last_name = random.choice(( 23 # 'doe', 'smith', 'blow', 'public', 24 # )) 25 last_name = pick_one( 26 'doe', 'smith', 'blow', 'public', 27 ) 28 # sex = 'F' 29 # sex = random.choice(('F', 'M')) 30 sex = pick_one('F', 'M') 31 # year_of_birth = 1996 32 33 a_person = dict( 34 first_name=first_name, 35 last_name=last_name, 36 sex=sex, 37 ) 38 39 this_year = datetime.datetime.now().year 40 year_of_birth = random.randint( 41 # datetime.datetime.now().year-120, 42 # datetime.datetime.now().year 43 this_year-120, this_year 44 )I use the new variable with a double starred expression to remove the repeating parts
39 this_year = datetime.datetime.now().year 40 year_of_birth = random.randint( 41 # datetime.datetime.now().year-120, 42 # datetime.datetime.now().year 43 this_year-120, this_year 44 ) 45 46 reality = src.person.factory( 47 # first_name=first_name, 48 # last_name=last_name, 49 # sex=sex, 50 **a_person, 51 year_of_birth=year_of_birth, 52 ) 53 my_expectation = dict( 54 # first_name=first_name, 55 # last_name=last_name, 56 # sex=sex, 57 **a_person, 58 # age=2026-year_of_birth, 59 # age=( 60 # datetime.datetime.now().year 61 # -year_of_birth 62 # ), 63 age=this_year-year_of_birth, 64 ) 65 self.assertEqual(reality, my_expectation) 66 67 68# Exceptions seenstill green.
I use the values of
first_name,last_nameand thesexvariables in thea_persondictionary13 def test_factory_w_keyword_arguments(self): 14 # first_name = 'jane' 15 # first_name = random.choice(( 16 # 'jane', 'joe', 'john', 'person', 17 # )) 18 # first_name = pick_one( 19 # 'jane', 'joe', 'john', 'person', 20 # ) 21 # last_name = 'doe' 22 # last_name = random.choice(( 23 # 'doe', 'smith', 'blow', 'public', 24 # )) 25 # last_name = pick_one( 26 # 'doe', 'smith', 'blow', 'public', 27 # ) 28 # sex = 'F' 29 # sex = random.choice(('F', 'M')) 30 # sex = pick_one('F', 'M') 31 # year_of_birth = 1996 32 33 a_person = dict( 34 # first_name=first_name, 35 # last_name=last_name, 36 # sex=sex, 37 first_name=pick_one( 38 'jane', 'joe', 'john', 'person', 39 ), 40 last_name=pick_one( 41 'doe', 'smith', 'blow', 'public', 42 ), 43 sex=pick_one('F', 'M'), 44 )still green.
I make a function with a tuple of all the names for the test to have more random names to pick from
7def pick_one(*choices): 8 return random.choice(choices) 9 10 11def get_random_name(): 12 return pick_one( 13 'jane', 'joe', 'john', 'person', 14 'doe', 'smith', 'blow', 'public', 15 ) 16 17 18class TestPerson(unittest.TestCase): 19 20 def test_factory_w_keyword_arguments(self):I use the function in the
a_persondictionary20 def test_factory_w_keyword_arguments(self): 21 # first_name = 'jane' 22 # first_name = random.choice(( 23 # 'jane', 'joe', 'john', 'person', 24 # )) 25 # first_name = pick_one( 26 # 'jane', 'joe', 'john', 'person', 27 # ) 28 # last_name = 'doe' 29 # last_name = random.choice(( 30 # 'doe', 'smith', 'blow', 'public', 31 # )) 32 # last_name = pick_one( 33 # 'doe', 'smith', 'blow', 'public', 34 # ) 35 # sex = 'F' 36 # sex = random.choice(('F', 'M')) 37 # sex = pick_one('F', 'M') 38 # year_of_birth = 1996 39 40 a_person = dict( 41 # first_name=first_name, 42 # last_name=last_name, 43 # sex=sex, 44 # first_name=pick_one( 45 # 'jane', 'joe', 'john', 'person', 46 # ), 47 # last_name=pick_one( 48 # 'doe', 'smith', 'blow', 'public', 49 # ), 50 first_name=get_random_name(), 51 last_name=get_random_name(), 52 sex=pick_one('F', 'M'), 53 )green.
I remove the commented lines
20 def test_factory_w_keyword_arguments(self): 21 a_person = dict( 22 first_name=get_random_name(), 23 last_name=get_random_name(), 24 sex=pick_one('F', 'M'), 25 ) 26 27 this_year = datetime.datetime.now().year 28 year_of_birth = random.randint( 29 this_year-120, this_year 30 ) 31 32 reality = src.person.factory( 33 **a_person, 34 year_of_birth=year_of_birth, 35 ) 36 my_expectation = dict( 37 **a_person, 38 age=this_year-year_of_birth, 39 ) 40 self.assertEqual(reality, my_expectation) 41 42 def test_factory_w_optional_arguments(self):I add a git commit message in the other terminal
git commit -am 'extract a_person dictionary'the terminal shows a summary of the changes then goes back to the command line.
test_factory_w_optional_arguments
I want to see what happens when I try to make a person without a value for the last_name argument
RED: make it fail
I go back to the terminal that is running the tests
I make a copy of test_factory_w_keyword_arguments and paste it below in
test_person.py32 reality = src.person.factory( 33 **a_person, 34 year_of_birth=year_of_birth, 35 ) 36 my_expectation = dict( 37 **a_person, 38 age=this_year-year_of_birth, 39 ) 40 self.assertEqual(reality, my_expectation) 41 42 def test_factory_w_keyword_arguments(self): 43 a_person = dict( 44 first_name=get_random_name(), 45 last_name=get_random_name(), 46 sex=pick_one('F', 'M'), 47 ) 48 49 this_year = datetime.datetime.now().year 50 year_of_birth = random.randint( 51 this_year-120, this_year 52 ) 53 54 reality = src.person.factory( 55 **a_person, 56 year_of_birth=year_of_birth, 57 ) 58 my_expectation = dict( 59 **a_person, 60 age=this_year-year_of_birth, 61 ) 62 self.assertEqual(reality, my_expectation) 63 64 65# Exceptions seenI change the name of the new test to test_factory_w_optional_arguments
32 reality = src.person.factory( 33 **a_person, 34 year_of_birth=year_of_birth, 35 ) 36 my_expectation = dict( 37 **a_person, 38 age=this_year-year_of_birth, 39 ) 40 self.assertEqual(reality, my_expectation) 41 42 def test_factory_w_optional_arguments(self): 43 a_person = dict( 44 first_name=get_random_name(), 45 last_name=get_random_name(), 46 sex=pick_one('F', 'M'), 47 )I comment out the
last_namekey-value pair in thea_persondictionary42 def test_factory_w_optional_arguments(self): 43 a_person = dict( 44 first_name=get_random_name(), 45 # last_name=get_random_name(), 46 sex=pick_one('F', 'M'), 47 )the terminal is my friend, and shows TypeError
TypeError: factory() missing 1 required positional argument: 'last_name'because this test no longer gives a value for
last_namewhen it calls the factory function, I have to make it a choice.
GREEN: make it pass
I add a default value for
last_namein the factory function inperson.pyto make it a choice4def factory( 5 first_name, last_name=None, 6 sex, year_of_birth, 7 ):the terminal is my friend, and shows SyntaxError
SyntaxError: parameter without a default follows parameter with a defaultbecause parameters without default values must come before parameters with default values
I add SyntaxError to the list of Exceptions seen in
test_person.py65# Exceptions seen 66# AssertionError 67# NameError 68# AttributeError 69# TypeError 70# SyntaxErrorI add a default value for
sexin the factory function inperson.py4def factory( 5 first_name, last_name=None, 6 sex=None, year_of_birth, 7 ):the terminal is my friend, and shows SyntaxError
SyntaxError: parameter without a default follows parameter with a defaultbecause parameters without default values must come before parameters with default values
I add a default value for
year_of_birth4def factory( 5 first_name, last_name=None, 6 sex=None, year_of_birth=None, 7 ):the terminal is my friend, and shows AssertionError
AssertionError: {'first_name': Z, 'last_name': None, 'sex': Y, 'age': X} != {'first_name': Z, 'sex': Y, 'age': X}where
Xis the random age,Yis the random sex andZis the random first namethe factory function returns a dictionary with a
'last_name'key, and the assertion expects a dictionary without that key
I add a key-value pair for
last_nametomy_expectationin test_factory_w_optional_arguments intest_person.py54 reality = src.person.factory( 55 **a_person, 56 year_of_birth=year_of_birth, 57 ) 58 my_expectation = dict( 59 **a_person, 60 last_name='doe', 61 age=this_year-year_of_birth, 62 ) 63 self.assertEqual(reality, my_expectation) 64 65 66# Exceptions seenthe terminal is my friend, and shows AssertionError
AssertionError: {'first_name': Z, 'last_name': None, 'sex': Y, 'age': X} != {'first_name': Z, 'sex': Y, 'last_name': 'doe', 'age': X}because the factory function returns a dictionary with a value of None for
last_nameand the assertion expects'doe'I change the default value for
last_namein the factory function inperson.py4def factory( 5 first_name, last_name='doe', 6 sex=None, year_of_birth=None, 7 ):the test passes because the default value for the
last_nameparameter of the function isdoe. This means thatsrc.person.factory( first_name=first_name, sex=sex, year_of_birth=year_of_birth, )is the same as
src.person.factory( first_name=first_name, sex=sex, year_of_birth=year_of_birth, last_name='doe', )A function uses the default value for a parameter when it is called without the parameter.
REFACTOR: make it better
I comment out the
sexkey in test_factory_w_optional_arguments to see what happens when I call the factory function without it, intest_person.py42 def test_factory_w_optional_arguments(self): 43 a_person = dict( 44 first_name=get_random_name(), 45 # last_name=get_random_name(), 46 # sex=pick_one('F', 'M'), 47 )the terminal is my friend, and shows AssertionError
AssertionError: {'first_name': Z, 'last_name': Y, 'sex': None, 'age': X} != {'first_name': Z, 'last_name': Y, 'age': X}the factory function returns a dictionary with a
'sex'key, and the assertion expects a dictionary without that keyI add a key-value pair for
sextomy_expectationin test_factory_w_optional_arguments54 reality = src.person.factory( 55 **a_person, 56 year_of_birth=year_of_birth, 57 ) 58 my_expectation = dict( 59 **a_person, 60 last_name='doe', 61 sex='M', 62 age=this_year-year_of_birth, 63 ) 64 self.assertEqual(reality, my_expectation) 65 66 67# Exceptions seenthe terminal is my friend, and shows AssertionError
AssertionError: {'first_name': Y, 'last_name': 'doe', 'sex': None, 'age': X} != {'first_name': Y, 'last_name': 'doe', 'sex': 'M', 'age': X}because the factory function returns a dictionary with a value of None for
sexand the assertion expects'M'I change the default value for
sexin the factory function inperson.py4def factory( 5 first_name, last_name='doe', 6 sex='M', year_of_birth=None, 7 ): 8 return { 9 'first_name': first_name, 10 'last_name': last_name, 11 'sex': sex, 12 'age': datetime.datetime.now().year-year_of_birth, 13 }the test passes because the default value for the
sexparameter of the function is'M'. This means thatsrc.person.factory( first_name=first_name, year_of_birth=year_of_birth, )is the same as
src.person.factory( first_name=first_name, year_of_birth=year_of_birth, last_name='doe', sex='M', )A function uses the default value for a parameter when it is called without the parameter.
I no longer need the
a_persondictionary in test_factory_w_optional_arguments because it has only one key. I add a variable forfirst_name42 def test_factory_w_optional_arguments(self): 43 first_name = get_random_name() 44 a_person = dict( 45 first_name=get_random_name(), 46 # last_name=get_random_name(), 47 # sex=pick_one('F', 'M'), 48 )I use the variable for
realityandmy_expectation55 reality = src.person.factory( 56 **a_person, 57 first_name=first_name, 58 year_of_birth=year_of_birth, 59 ) 60 my_expectation = dict( 61 **a_person, 62 first_name=first_name, 63 last_name='doe', 64 sex='M', 65 age=this_year-year_of_birth, 66 ) 67 self.assertEqual(reality, my_expectation) 68 69 70# Exceptions seenthe terminal is my friend, and shows TypeError
TypeError: src.person.factory() got multiple values for keyword argument 'first_name'because the
a_persondictionary already has a key calledfirst_name, the call tosrc.person.factorygets called with the same name two timesI comment out
**a_person,55 reality = src.person.factory( 56 # **a_person, 57 first_name=first_name, 58 year_of_birth=year_of_birth, 59 ) 60 my_expectation = dict( 61 # **a_person, 62 first_name=first_name, 63 last_name='doe', 64 sex='M', 65 age=this_year-year_of_birth, 66 ) 67 self.assertEqual(reality, my_expectation) 68 69 70# Exceptions seenI use ctrl+s (Windows/Linux) or command+s (MacOS) to run the test a few times and it passes with no more random failures
I remove the commented lines and the
a_persondictionary42 def test_factory_w_optional_arguments(self): 43 first_name = get_random_name() 44 45 this_year = datetime.datetime.now().year 46 year_of_birth = random.randint( 47 this_year-120, this_year 48 ) 49 50 reality = src.person.factory( 51 first_name=first_name, 52 year_of_birth=year_of_birth, 53 ) 54 my_expectation = dict( 55 first_name=first_name, 56 last_name='doe', 57 sex='M', 58 age=this_year-year_of_birth, 59 ) 60 self.assertEqual(reality, my_expectation) 61 62 63# Exceptions seenI add a git commit message in the other terminal
git commit -am 'add test_factory_w_optional_arguments'the terminal shows a summary of the changes then goes back to the command line.
test_factory_person_says_hello
I have a function that takes in first_name, last_name, sex and year_of_birth for a person and returns a dictionary with first_name, last_name, sex and age (based on the year_of_birth) as keys.
What if I want the person to say hello, How would I do that? I can write a function that takes in a person (dictionary) and returns a message (string).
RED: make it fail
I add a new test to
test_person.py50 reality = src.person.factory( 51 first_name=first_name, 52 year_of_birth=year_of_birth, 53 ) 54 my_expectation = dict( 55 first_name=first_name, 56 last_name='doe', 57 sex='M', 58 age=this_year-year_of_birth, 59 ) 60 self.assertEqual(reality, my_expectation) 61 62 def test_factory_person_says_hello(self): 63 joe = src.person.factory( 64 first_name='joe', 65 last_name='blow', 66 year_of_birth=1996, 67 ) 68 69 reality = src.person.say_hello(joe) 70 my_expectation = None 71 self.assertEqual(reality, my_expectation) 72 73 74# Exceptions seenthe terminal is my friend, and shows AttributeError
AttributeError: module 'src.person' has no attribute 'hello'because
person.pydoes not have a function namedsay_hello
GREEN: make it pass
I add the function to
person.py4def factory( 5 first_name, last_name='doe', 6 sex='M', year_of_birth=None, 7 ): 8 return { 9 'first_name': first_name, 10 'last_name': last_name, 11 'sex': sex, 12 'age': ( 13 datetime.datetime.today().year 14 -year_of_birth 15 ), 16 } 17 18 19def say_hello(): 20 return Nonethe terminal is my friend, and shows TypeError
TypeError: hello() takes 0 positional arguments but 1 was givenbecause the definition for
say_hellodoes not allow inputs and the test called the function with a positional argument (person)I add a name to the definition
19def say_hello(person): 20 return Nonethe test passes.
REFACTOR: make it better
I want the say_hello function to return a string for the person (dictionary) I give as input
I change
my_expectationto an f-string in test_factory_person_says_hello intest_person.py62 def test_factory_person_says_hello(self): 63 joe = src.person.factory( 64 first_name='joe', 65 last_name='blow', 66 year_of_birth=1996, 67 ) 68 69 reality = src.person.say_hello(joe) 70 my_expectation = ( 71 'Hi, my name is joe blow and I am' 72 f' {datetime.datetime.now().year-1996}' 73 ) 74 self.assertEqual(reality, my_expectation) 75 76 77# Exceptions seenthe terminal is my friend, and shows AssertionError
AssertionError: None != 'Hi, my name is joe blow and I am 30'I copy the value from the terminal and paste it in the return statement in
person.py19def say_hello(person): 20 return 'Hi, my name is joe blow and I am 30'the test passes.
I add an assertion for another person
62 def test_factory_person_says_hello(self): 63 joe = src.person.factory( 64 first_name='joe', 65 last_name='blow', 66 year_of_birth=1996, 67 ) 68 69 reality = src.person.say_hello(joe) 70 my_expectation = ( 71 'Hi, my name is joe blow and I am' 72 f' {datetime.datetime.now().year-1996}' 73 ) 74 self.assertEqual(reality, my_expectation) 75 76 jane = src.person.factory( 77 first_name='jane', 78 sex='F', 79 year_of_birth=1991, 80 ) 81 82 reality = src.person.say_hello(jane) 83 my_expectation = ( 84 'Hi, my name is jane doe and I am' 85 f' {datetime.datetime.now().year-1991}' 86 ) 87 self.assertEqual(reality, my_expectation) 88 89 90# Exceptions seenthe terminal is my friend, and shows AssertionError
AssertionError: 'Hi, my name is joe blow and I am 30' != 'Hi, my name is jane doe and I am 35'I have to make sure the
say_hellofunction uses the values of thepersondictionary to make the message. I can do that with the get method of dictionaries.I change the string to an f-string with the value for the
first_namekey from the dictionary thesay_hellofunction receives, inperson.py19def say_hello(person): 20 first_name = person.get('first_name') 21 22 # return 'Hi, my name is joe blow and I am 30' 23 return f'Hi, my name is {first_name} blow and I am 30'the terminal is my friend, and shows AssertionError
AssertionError: 'Hi, my name is jane blow and I am 30' != 'Hi, my name is jane doe and I am 35'the first names are the same, the last name and ages are different
I add the value for the
last_namekey from the dictionary19def say_hello(person): 20 first_name = person.get('first_name') 21 last_name = person.get('last_name') 22 23 # return 'Hi, my name is joe blow and I am 30' 24 # return f'Hi, my name is {first_name} blow and I am 30' 25 return ( 26 f'Hi, my name is {first_name} {last_name}' 27 f' and I am 30' 28 )the terminal is my friend, and shows AssertionError
AssertionError: 'Hi, my name is jane doe and I am 30' != 'Hi, my name is jane doe and I am 35'the age is the only thing that is different
I add the value for the
agekey from the dictionary19def say_hello(person): 20 first_name = person.get('first_name') 21 last_name = person.get('last_name') 22 age = person.get('age') 23 24 # return 'Hi, my name is joe blow and I am 30' 25 # return f'Hi, my name is {first_name} blow and I am 30' 26 return ( 27 f'Hi, my name is {first_name} {last_name}' 28 # f' and I am 30' 29 f' and I am {age}' 30 )the test passes.
I remove the commented lines
19def say_hello(person): 20 first_name = person.get('first_name') 21 last_name = person.get('last_name') 22 age = person.get('age') 23 24 return ( 25 f'Hi, my name is {first_name} {last_name}' 26 f' and I am {age}' 27 )I add an assertion for a new person in test_factory_person_says_hello in
test_person.py76 jane = src.person.factory( 77 first_name='jane', 78 sex='F', 79 year_of_birth=1991, 80 ) 81 82 reality = src.person.say_hello(jane) 83 my_expectation = ( 84 'Hi, my name is jane doe and I am' 85 f' {datetime.datetime.now().year-1991}' 86 ) 87 self.assertEqual(reality, my_expectation) 88 89 john = src.person.factory( 90 first_name='john', 91 last_name='smith', 92 year_of_birth=1580, 93 ) 94 95 reality = src.person.say_hello(john) 96 my_expectation = ( 97 'Hi, my name is jane doe and I am' 98 f'{datetime.datetime.now().year-1991}' 99 ) 100 self.assertEqual(reality, my_expectation) 101 102 103# Exceptions seenthe terminal is my friend, and shows AssertionError
AssertionError: 'Hi, my name is john smith and I am 446' != 'Hi, my name is jane doe and I am 35'I change
my_expectationto matchreality89 john = src.person.factory( 90 first_name='john', 91 last_name='smith', 92 year_of_birth=1580, 93 ) 94 95 reality = src.person.say_hello(john) 96 my_expectation = ( 97 'Hi, my name is john smith and I am' 98 f' {datetime.datetime.now().year-1580}' 99 ) 100 self.assertEqual(reality, my_expectation) 101 102 103# Exceptions seenthe test passes.
I add an assertion for one more person
89 john = src.person.factory( 90 first_name='john', 91 last_name='smith', 92 year_of_birth=1580, 93 ) 94 95 reality = src.person.say_hello(john) 96 my_expectation = ( 97 'Hi, my name is john smith and I am' 98 f' {datetime.datetime.now().year-1580}' 99 ) 100 self.assertEqual(reality, my_expectation) 101 102 a_person = src.person.factory( 103 first_name='person', 104 last_name='public', 105 year_of_birth=2000, 106 sex='F', 107 ) 108 109 reality = src.person.say_hello(a_person) 110 my_expectation = ( 111 'Hi, my name is john smith and I am' 112 f' {datetime.datetime.now().year-1580}' 113 ) 114 self.assertEqual(reality, my_expectation) 115 116 117# Exceptions seenthe terminal is my friend, and shows AssertionError
AssertionError: 'Hi, my name is person public and I am 26' != 'Hi, my name is john smith and I am 446'I change
my_expectationto matchreality102 a_person = src.person.factory( 103 first_name='person', 104 last_name='public', 105 year_of_birth=2000, 106 sex='F', 107 ) 108 109 reality = src.person.say_hello(a_person) 110 my_expectation = ( 111 'Hi, my name is person public and I am' 112 f' {datetime.datetime.now().year-2000}' 113 ) 114 self.assertEqual(reality, my_expectation) 115 116 117# Exceptions seenthe test passes.
I add variables to use them to remove repetition of
'person','public',2000and the age calculation from the last assertion89 john = src.person.factory( 90 first_name='john', 91 last_name='smith', 92 year_of_birth=1580, 93 ) 94 95 reality = src.person.say_hello(john) 96 my_expectation = ( 97 'Hi, my name is john smith and I am' 98 f' {datetime.datetime.now().year-1580}' 99 ) 100 self.assertEqual(reality, my_expectation) 101 102 first_name = 'person' 103 last_name = 'public' 104 year_of_birth = 2000 105 age = ( 106 datetime.datetime.now().year 107 - year_of_birth 108 ) 109 110 a_person = src.person.factory( 111 first_name='person', 112 last_name='public', 113 year_of_birth=2000, 114 sex='F', 115 )I use the variables to remove repetition of
'person','public',2000and the age calculation102 first_name = 'person' 103 last_name = 'public' 104 year_of_birth = 2000 105 age = ( 106 datetime.datetime.now().year 107 - year_of_birth 108 ) 109 110 a_person = src.person.factory( 111 # first_name='person', 112 # last_name='public', 113 # year_of_birth=2000, 114 first_name=first_name, 115 last_name=last_name, 116 year_of_birth=year_of_birth, 117 sex='F', 118 ) 119 120 reality = src.person.say_hello(a_person) 121 my_expectation = ( 122 # 'Hi, my name is person public and I am' 123 # f' {datetime.datetime.now().year-2000}' 124 f'Hi, my name is {first_name} {last_name}' 125 f' and I am {age}' 126 ) 127 self.assertEqual(reality, my_expectation) 128 129 130# Exceptions seenthe test is still green.
I add variables to use them to remove repetition of
'john','smith',1580and the age calculation from the assertion before the one fora_person76 jane = src.person.factory( 77 first_name='jane', 78 sex='F', 79 year_of_birth=1991, 80 ) 81 82 reality = src.person.say_hello(jane) 83 my_expectation = ( 84 'Hi, my name is jane doe and I am' 85 f' {datetime.datetime.now().year-1991}' 86 ) 87 self.assertEqual(reality, my_expectation) 88 89 first_name = 'john' 90 last_name = 'smith' 91 year_of_birth = 1580 92 age = ( 93 datetime.datetime.now().year 94 - year_of_birth 95 ) 96 97 john = src.person.factory( 98 first_name='john', 99 last_name='smith', 100 year_of_birth=1580, 101 )I use the variables to remove repetition of
'john','smith',1580and the age calculation89 first_name = 'john' 90 last_name = 'smith' 91 year_of_birth = 1580 92 age = ( 93 datetime.datetime.now().year 94 - year_of_birth 95 ) 96 97 john = src.person.factory( 98 # first_name='john', 99 # last_name='smith', 100 # year_of_birth=1580, 101 first_name=first_name, 102 last_name=last_name, 103 year_of_birth=year_of_birth, 104 ) 105 106 reality = src.person.say_hello(john) 107 my_expectation = ( 108 # 'Hi, my name is john smith and I am' 109 # f' {datetime.datetime.now().year-1580}' 110 f'Hi, my name is {first_name} {last_name}' 111 f' and I am {age}' 112 ) 113 self.assertEqual(reality, my_expectation) 114 115 first_name = 'person' 116 last_name = 'public' 117 year_of_birth = 2000 118 age = ( 119 datetime.datetime.now().year 120 - year_of_birth 121 )still green.
I add the same variable names to use them to remove repetition of
'jane',1991and the age calculation from the second assertion62 def test_factory_person_says_hello(self): 63 joe = src.person.factory( 64 first_name='joe', 65 last_name='blow', 66 year_of_birth=1996, 67 ) 68 69 reality = src.person.say_hello(joe) 70 my_expectation = ( 71 'Hi, my name is joe blow and I am' 72 f' {datetime.datetime.now().year-1996}' 73 ) 74 self.assertEqual(reality, my_expectation) 75 76 first_name = 'jane' 77 last_name = 'doe' 78 year_of_birth = 1991 79 age = ( 80 datetime.datetime.now().year 81 - year_of_birth 82 ) 83 84 jane = src.person.factory( 85 first_name='jane', 86 sex='F', 87 year_of_birth=1991, 88 )I use the variables to remove repetition of
'jane',1991and the age calculation76 first_name = 'jane' 77 last_name = 'doe' 78 year_of_birth = 1991 79 age = ( 80 datetime.datetime.now().year 81 - year_of_birth 82 ) 83 84 jane = src.person.factory( 85 # first_name='jane', 86 sex='F', 87 # year_of_birth=1991, 88 first_name=first_name, 89 year_of_birth=year_of_birth, 90 ) 91 92 reality = src.person.say_hello(jane) 93 my_expectation = ( 94 # 'Hi, my name is jane doe and I am' 95 # f' {datetime.datetime.now().year-1991}' 96 f'Hi, my name is {first_name} {last_name}' 97 f' and I am {age}' 98 ) 99 self.assertEqual(reality, my_expectation) 100 101 first_name = 'john' 102 last_name = 'smith' 103 year_of_birth = 1580 104 age = ( 105 datetime.datetime.now().year 106 - year_of_birth 107 )green.
I add the variable names to use them to remove repetition of
'joe','blow',1996and the age calculation from the first assertion64 def test_factory_person_says_hello(self): 65 first_name = 'joe' 66 last_name = 'blow' 67 year_of_birth = 1996 68 age = ( 69 datetime.datetime.now().year 70 - year_of_birth 71 ) 72 73 joe = src.person.factory( 74 first_name='joe', 75 last_name='blow', 76 year_of_birth=1996, 77 )I use the variables to remove repetition of
'joe','blow',1996and the age calculation62 def test_factory_person_says_hello(self): 63 first_name = 'joe' 64 last_name = 'blow' 65 year_of_birth = 1996 66 age = ( 67 datetime.datetime.now().year 68 - year_of_birth 69 ) 70 71 joe = src.person.factory( 72 # first_name='joe', 73 # last_name='blow', 74 # year_of_birth=1996, 75 first_name=first_name, 76 last_name=last_name, 77 year_of_birth=year_of_birth, 78 ) 79 80 reality = src.person.say_hello(joe) 81 my_expectation = ( 82 # 'Hi, my name is joe blow and I am' 83 # f' {datetime.datetime.now().year-1996}' 84 f'Hi, my name is {first_name} {last_name}' 85 f' and I am {age}' 86 ) 87 self.assertEqual(reality, my_expectation) 88 89 first_name = 'jane' 90 last_name = 'doe' 91 year_of_birth = 1991 92 age = ( 93 datetime.datetime.now().year 94 - year_of_birth 95 )the test is still green.
I remove the commented lines
62 def test_factory_person_says_hello(self): 63 first_name = 'joe' 64 last_name = 'blow' 65 year_of_birth = 1996 66 age = ( 67 datetime.datetime.now().year 68 - year_of_birth 69 ) 70 71 joe = src.person.factory( 72 first_name=first_name, 73 last_name=last_name, 74 year_of_birth=year_of_birth, 75 ) 76 77 reality = src.person.say_hello(joe) 78 my_expectation = ( 79 f'Hi, my name is {first_name} {last_name}' 80 f' and I am {age}' 81 ) 82 self.assertEqual(reality, my_expectation) 83 84 first_name = 'jane' 85 last_name = 'doe' 86 year_of_birth = 1991 87 age = ( 88 datetime.datetime.now().year 89 - year_of_birth 90 ) 91 92 jane = src.person.factory( 93 sex='F', 94 first_name=first_name, 95 year_of_birth=year_of_birth, 96 ) 97 98 reality = src.person.say_hello(jane) 99 my_expectation = ( 100 f'Hi, my name is {first_name} {last_name}' 101 f' and I am {age}' 102 ) 103 self.assertEqual(reality, my_expectation) 104 105 first_name = 'john' 106 last_name = 'smith' 107 year_of_birth = 1580 108 age = ( 109 datetime.datetime.now().year 110 - year_of_birth 111 ) 112 113 john = src.person.factory( 114 first_name=first_name, 115 last_name=last_name, 116 year_of_birth=year_of_birth, 117 ) 118 119 reality = src.person.say_hello(john) 120 my_expectation = ( 121 f'Hi, my name is {first_name} {last_name}' 122 f' and I am {age}' 123 ) 124 self.assertEqual(reality, my_expectation) 125 126 first_name = 'person' 127 last_name = 'public' 128 year_of_birth = 2000 129 age = ( 130 datetime.datetime.now().year 131 - year_of_birth 132 ) 133 134 a_person = src.person.factory( 135 first_name=first_name, 136 last_name=last_name, 137 year_of_birth=year_of_birth, 138 sex='F', 139 ) 140 141 reality = src.person.say_hello(a_person) 142 my_expectation = ( 143 f'Hi, my name is {first_name} {last_name}' 144 f' and I am {age}' 145 ) 146 self.assertEqual(reality, my_expectation) 147 148 149# Exceptions seenI add a git commit message in the other terminal
git commit -am 'add test_factory_person_says_hello'the terminal shows a summary of the changes then goes back to the command line.
extract calculate_age function
Each assertion in every test has a calculation for the age
I go back to the terminal that is running the tests
I add a function to use to remove repetition of the age calculation
11def get_random_name(): 12 return pick_one( 13 'jane', 'joe', 'john', 'person', 14 'doe', 'smith', 'blow', 'public', 15 ) 16 17 18def calculate_age(year_of_birth): 19 return ( 20 datetime.datetime.now().year 21 - year_of_birth 22 ) 23 24 25class TestPerson(unittest.TestCase): 26 27 def test_factory_w_keyword_arguments(self):I use the function to remove repetition of the age calculation from
my_expectationin test_factory_w_keyword_arguments39 reality = src.person.factory( 40 **a_person, 41 year_of_birth=year_of_birth, 42 ) 43 my_expectation = dict( 44 **a_person, 45 # age=this_year-year_of_birth, 46 age=calculate_age(year_of_birth), 47 ) 48 self.assertEqual(reality, my_expectation)I remove the commented line
27 def test_factory_w_keyword_arguments(self): 28 a_person = dict( 29 first_name=get_random_name(), 30 last_name=get_random_name(), 31 sex=pick_one('F', 'M'), 32 ) 33 34 this_year = datetime.datetime.now().year 35 year_of_birth = random.randint( 36 this_year-120, this_year 37 ) 38 39 reality = src.person.factory( 40 **a_person, 41 year_of_birth=year_of_birth, 42 ) 43 my_expectation = dict( 44 **a_person, 45 age=calculate_age(year_of_birth), 46 ) 47 self.assertEqual(reality, my_expectation) 48 49 def test_factory_w_optional_arguments(self):the test is still green.
I use the function to remove repetition of the age calculation from
my_expectationin test_factory_w_optional_arguments57 reality = src.person.factory( 58 first_name=first_name, 59 year_of_birth=year_of_birth, 60 ) 61 my_expectation = dict( 62 first_name=first_name, 63 last_name='doe', 64 sex='M', 65 # age=this_year-year_of_birth, 66 age=calculate_age(year_of_birth), 67 ) 68 self.assertEqual(reality, my_expectation) 69 70 def test_factory_person_says_hello(self):I remove the commented line
49 def test_factory_w_optional_arguments(self): 50 first_name = get_random_name() 51 52 this_year = datetime.datetime.now().year 53 year_of_birth = random.randint( 54 this_year-120, this_year 55 ) 56 57 reality = src.person.factory( 58 first_name=first_name, 59 year_of_birth=year_of_birth, 60 ) 61 my_expectation = dict( 62 first_name=first_name, 63 last_name='doe', 64 sex='M', 65 age=calculate_age(year_of_birth), 66 ) 67 self.assertEqual(reality, my_expectation) 68 69 def test_factory_person_says_hello(self):the test is still green.
I use the function to remove repetition of the age calculation from
my_expectationforjoein test_factory_person_says_hello69 def test_factory_person_says_hello(self): 70 first_name = 'joe' 71 last_name = 'blow' 72 year_of_birth = 1996 73 # age = ( 74 # datetime.datetime.now().year 75 # - year_of_birth 76 # ) 77 78 joe = src.person.factory( 79 first_name=first_name, 80 last_name=last_name, 81 year_of_birth=year_of_birth, 82 ) 83 84 reality = src.person.say_hello(joe) 85 my_expectation = ( 86 f'Hi, my name is {first_name} {last_name}' 87 # f' and I am {age}' 88 f' and I am {calculate_age(year_of_birth)}' 89 ) 90 self.assertEqual(reality, my_expectation)the test is still green.
I use the function to remove repetition of the age calculation from
my_expectationforjanein test_factory_person_says_hello84 reality = src.person.say_hello(joe) 85 my_expectation = ( 86 f'Hi, my name is {first_name} {last_name}' 87 # f' and I am {age}' 88 f' and I am {calculate_age(year_of_birth)}' 89 ) 90 self.assertEqual(reality, my_expectation) 91 92 first_name = 'jane' 93 last_name = 'doe' 94 year_of_birth = 1991 95 # age = ( 96 # datetime.datetime.now().year 97 # - year_of_birth 98 # ) 99 100 jane = src.person.factory( 101 sex='F', 102 first_name=first_name, 103 year_of_birth=year_of_birth, 104 ) 105 106 reality = src.person.say_hello(jane) 107 my_expectation = ( 108 f'Hi, my name is {first_name} {last_name}' 109 # f' and I am {age}' 110 f' and I am {calculate_age(year_of_birth)}' 111 ) 112 self.assertEqual(reality, my_expectation)still green.
I use the function to remove repetition of the age calculation from
my_expectationforjohnin test_factory_person_says_hello106 reality = src.person.say_hello(jane) 107 my_expectation = ( 108 f'Hi, my name is {first_name} {last_name}' 109 # f' and I am {age}' 110 f' and I am {calculate_age(year_of_birth)}' 111 ) 112 self.assertEqual(reality, my_expectation) 113 114 first_name = 'john' 115 last_name = 'smith' 116 year_of_birth = 1580 117 # age = ( 118 # datetime.datetime.now().year 119 # - year_of_birth 120 # ) 121 122 john = src.person.factory( 123 first_name=first_name, 124 last_name=last_name, 125 year_of_birth=year_of_birth, 126 ) 127 128 reality = src.person.say_hello(john) 129 my_expectation = ( 130 f'Hi, my name is {first_name} {last_name}' 131 # f' and I am {age}' 132 f' and I am {calculate_age(year_of_birth)}' 133 ) 134 self.assertEqual(reality, my_expectation)green.
I use the function to remove repetition of the age calculation from
my_expectationfora_personin test_factory_person_says_hello136 first_name = 'person' 137 last_name = 'public' 138 year_of_birth = 2000 139 # age = ( 140 # datetime.datetime.now().year 141 # - year_of_birth 142 # ) 143 144 a_person = src.person.factory( 145 first_name=first_name, 146 last_name=last_name, 147 year_of_birth=year_of_birth, 148 sex='F', 149 ) 150 151 reality = src.person.say_hello(a_person) 152 my_expectation = ( 153 f'Hi, my name is {first_name} {last_name}' 154 # f' and I am {age}' 155 f' and I am {calculate_age(year_of_birth)}' 156 ) 157 self.assertEqual(reality, my_expectation) 158 159 160# Exceptions seenthe test is still green.
I remove the commented lines
69 def test_factory_person_says_hello(self): 70 first_name = 'joe' 71 last_name = 'blow' 72 year_of_birth = 1996 73 74 joe = src.person.factory( 75 first_name=first_name, 76 last_name=last_name, 77 year_of_birth=year_of_birth, 78 ) 79 80 reality = src.person.say_hello(joe) 81 my_expectation = ( 82 f'Hi, my name is {first_name} {last_name}' 83 f' and I am {calculate_age(year_of_birth)}' 84 ) 85 self.assertEqual(reality, my_expectation) 86 87 first_name = 'jane' 88 last_name = 'doe' 89 year_of_birth = 1991 90 91 jane = src.person.factory( 92 sex='F', 93 first_name=first_name, 94 year_of_birth=year_of_birth, 95 ) 96 97 reality = src.person.say_hello(jane) 98 my_expectation = ( 99 f'Hi, my name is {first_name} {last_name}' 100 f' and I am {calculate_age(year_of_birth)}' 101 ) 102 self.assertEqual(reality, my_expectation) 103 104 first_name = 'john' 105 last_name = 'smith' 106 year_of_birth = 1580 107 108 john = src.person.factory( 109 first_name=first_name, 110 last_name=last_name, 111 year_of_birth=year_of_birth, 112 ) 113 114 reality = src.person.say_hello(john) 115 my_expectation = ( 116 f'Hi, my name is {first_name} {last_name}' 117 f' and I am {calculate_age(year_of_birth)}' 118 ) 119 self.assertEqual(reality, my_expectation) 120 121 first_name = 'person' 122 last_name = 'public' 123 year_of_birth = 2000 124 125 a_person = src.person.factory( 126 first_name=first_name, 127 last_name=last_name, 128 year_of_birth=year_of_birth, 129 sex='F', 130 ) 131 132 reality = src.person.say_hello(a_person) 133 my_expectation = ( 134 f'Hi, my name is {first_name} {last_name}' 135 f' and I am {calculate_age(year_of_birth)}' 136 ) 137 self.assertEqual(reality, my_expectation) 138 139 140# Exceptions seenthe test is still green.
I add a git commit message in the other terminal
git commit -am 'extract calculate_age function'the terminal shows a summary of the changes then goes back to the command line.
test say_hello with random values
I want to use random values in test_factory_person_says_hello
I go back to the terminal that is running the tests
I add random values to use for the
first_name,last_name,sexandyear_of_birthvariables69 def test_factory_person_says_hello(self): 70 first_name = get_random_name() 71 last_name = get_random_name() 72 sex = pick_one('F', 'M') 73 this_year = datetime.datetime.now().year 74 year_of_birth = random.randint( 75 this_year-120, this_year 76 ) 77 78 first_name = 'joe' 79 last_name = 'blow' 80 year_of_birth = 1996I comment out the
first_name,last_nameandyear_of_birthvariables forjoein the first assertion, so it uses the ones with random values69 def test_factory_person_says_hello(self): 70 first_name = get_random_name() 71 last_name = get_random_name() 72 sex = pick_one('F', 'M') 73 this_year = datetime.datetime.now().year 74 year_of_birth = random.randint( 75 this_year-120, this_year 76 ) 77 78 # first_name = 'joe' 79 # last_name = 'blow' 80 # year_of_birth = 1996 81 82 joe = src.person.factory( 83 first_name=first_name, 84 last_name=last_name, 85 year_of_birth=year_of_birth, 86 )the test is still green.
I add a random value for the
sexparameter with thesexvariable in the call tosrc.person.factoryforjoe82 joe = src.person.factory( 83 first_name=first_name, 84 last_name=last_name, 85 year_of_birth=year_of_birth, 86 sex=sex 87 )still green.
I comment out the
first_name,last_nameandyear_of_birthvariables forjanein the second assertion, so it uses the ones with random values89 reality = src.person.say_hello(joe) 90 my_expectation = ( 91 f'Hi, my name is {first_name} {last_name}' 92 f' and I am {calculate_age(year_of_birth)}' 93 ) 94 self.assertEqual(reality, my_expectation) 95 96 # first_name = 'jane' 97 # last_name = 'doe' 98 # year_of_birth = 1991 99 100 jane = src.person.factory( 101 sex='F', 102 first_name=first_name, 103 year_of_birth=year_of_birth, 104 )I use ctrl+s (Windows/Linux) or command+s (MacOS) to run the test a few times and it passes if the
last_nameis randomly'doe'.If the
last_nameis not'doe', the terminal is my friend, and shows AssertionErrorAssertionError: 'Hi, my name is X doe and I am Z' != 'Hi, my name is X Y and I am Z'because I did not need to give a value for the
last_nameparameter in the call tosrc.person.factorysince the default value for thelast_nameparameter of the function is'doe'. This means thatsrc.person.factory( first_name=first_name, sex='F', year_of_birth=year_of_birth, )is the same as
src.person.factory( first_name=first_name, sex='F', year_of_birth=year_of_birth, last_name='doe' )A function uses the default value for a parameter when it is called without the parameter.
I add
last_nameto the call tosrc.person.factoryforjaneto use the variable100 jane = src.person.factory( 101 sex='F', 102 first_name=first_name, 103 year_of_birth=year_of_birth, 104 last_name=last_name, 105 )I use ctrl+s (Windows/Linux) or command+s (MacOS) to run the test a few times and it passes with no more random failures.
I change the value for the
sexparameter to thesexvariable so it uses a random value in the call tosrc.person.factoryforjane100 jane = src.person.factory( 101 # sex='F', 102 first_name=first_name, 103 year_of_birth=year_of_birth, 104 last_name=last_name, 105 sex=sex, 106 )the test is still green.
I comment out the
first_name,last_nameandyear_of_birthvariables forjohnin the third assertion, so it uses the ones with random values108 reality = src.person.say_hello(jane) 109 my_expectation = ( 110 f'Hi, my name is {first_name} {last_name}' 111 f' and I am {calculate_age(year_of_birth)}' 112 ) 113 self.assertEqual(reality, my_expectation) 114 115 # first_name = 'john' 116 # last_name = 'smith' 117 # year_of_birth = 1580 118 119 john = src.person.factory( 120 first_name=first_name, 121 last_name=last_name, 122 year_of_birth=year_of_birth, 123 )still green.
I add a random value for the
sexparameter with thesexvariable in the call tosrc.person.factoryforjohn119 john = src.person.factory( 120 first_name=first_name, 121 last_name=last_name, 122 year_of_birth=year_of_birth, 123 sex=sex, 124 )green.
I comment out the
first_name,last_nameandyear_of_birthvariables fora_personin the last assertion, so it uses the ones with random values126 reality = src.person.say_hello(john) 127 my_expectation = ( 128 f'Hi, my name is {first_name} {last_name}' 129 f' and I am {calculate_age(year_of_birth)}' 130 ) 131 self.assertEqual(reality, my_expectation) 132 133 # first_name = 'person' 134 # last_name = 'public' 135 # year_of_birth = 2000 136 137 a_person = src.person.factory( 138 first_name=first_name, 139 last_name=last_name, 140 year_of_birth=year_of_birth, 141 sex='F', 142 )the test is still green.
I add a random value for the
sexparameter with thesexvariable in the call tosrc.person.factoryfora_person137 a_person = src.person.factory( 138 first_name=first_name, 139 last_name=last_name, 140 year_of_birth=year_of_birth, 141 # sex='F', 142 sex=sex, 143 )still green.
I add a random person that covers all the possible cases because the four people I made in test_factory_person_says_hello now all use random values, and the assertions are the same for each case because they use the
first_name,last_nameandagevariables that are sent in the call tosrc.person.factory145 reality = src.person.say_hello(a_person) 146 my_expectation = ( 147 f'Hi, my name is {first_name} {last_name}' 148 f' and I am {calculate_age(year_of_birth)}' 149 ) 150 self.assertEqual(reality, my_expectation) 151 152 a_random_person = src.person.factory( 153 first_name=first_name, 154 last_name=last_name, 155 sex=sex, 156 year_of_birth=year_of_birth, 157 ) 158 159 reality = src.person.say_hello(a_random_person) 160 my_expectation = '' 161 self.assertEqual(reality, my_expectation) 162 163 164# Exceptions seenthe terminal is my friend and shows AssertionError
AssertionError: 'Hi, my name is Z Y and I am X' != ''I change
my_expectationto matchreality152 a_random_person = src.person.factory( 153 first_name=first_name, 154 last_name=last_name, 155 sex=sex, 156 year_of_birth=year_of_birth, 157 ) 158 159 reality = src.person.say_hello(a_random_person) 160 my_expectation = ( 161 f'Hi, my name is {first_name} {last_name}' 162 f' and I am {calculate_age(year_of_birth)}' 163 ) 164 self.assertEqual(reality, my_expectation) 165 166 167# Exceptions seenthe test is green
I remove the commented lines and the other people from test_factory_person_says_hello because they are now all the same
69 def test_factory_person_says_hello(self): 70 first_name = get_random_name() 71 last_name = get_random_name() 72 sex = pick_one('F', 'M') 73 this_year = datetime.datetime.now().year 74 year_of_birth = random.randint( 75 this_year-120, this_year 76 ) 77 78 a_random_person = src.person.factory( 79 first_name=first_name, 80 last_name=last_name, 81 sex=sex, 82 year_of_birth=year_of_birth, 83 ) 84 85 reality = src.person.say_hello(a_random_person) 86 my_expectation = ( 87 f'Hi, my name is {first_name} {last_name}' 88 f' and I am {calculate_age(year_of_birth)}' 89 ) 90 self.assertEqual(reality, my_expectation) 91 92 93# Exceptions seenI add a git commit message in the other terminal
git commit -am 'test say_hello with random values'the terminal shows a summary of the changes then goes back to the command line.
extract get_random_year_of_birth function
I make the this_year and year_of_birth variables the same way in all three tests.
I go back to the terminal that is running the tests
I add a function to use to replace the repetition of making the values for the
this_yearandyear_of_birthvariables intest_person.py18def calculate_age(year_of_birth): 19 return ( 20 datetime.datetime.now().year 21 - year_of_birth 22 ) 23 24 25def get_random_year_of_birth(): 26 this_year = datetime.datetime.now().year 27 return random.randint( 28 this_year-120, this_year 29 ) 30 31 32class TestPerson(unittest.TestCase): 33 34 def test_factory_w_keyword_arguments(self):I use the
get_random_year_of_birthfunction to replace the repetition of making the values for thethis_yearandyear_of_birthin test_factory_w_keyword_arguments34 def test_factory_w_keyword_arguments(self): 35 a_person = dict( 36 first_name=get_random_name(), 37 last_name=get_random_name(), 38 sex=pick_one('F', 'M'), 39 ) 40 41 # this_year = datetime.datetime.now().year 42 # year_of_birth = random.randint( 43 # this_year-120, this_year 44 # ) 45 year_of_birth = get_random_year_of_birth()the test is still green.
I remove the commented lines
34 def test_factory_w_keyword_arguments(self): 35 a_person = dict( 36 first_name=get_random_name(), 37 last_name=get_random_name(), 38 sex=pick_one('F', 'M'), 39 ) 40 year_of_birth = get_random_year_of_birth() 41 42 reality = src.person.factory( 43 **a_person, 44 year_of_birth=year_of_birth, 45 ) 46 my_expectation = dict( 47 **a_person, 48 age=calculate_age(year_of_birth), 49 ) 50 self.assertEqual(reality, my_expectation) 51 52 def test_factory_w_optional_arguments(self):I use the
get_random_year_of_birthfunction to replace the repetition of making the values for thethis_yearandyear_of_birthin test_factory_w_optional_arguments52 def test_factory_w_optional_arguments(self): 53 first_name = get_random_name() 54 55 # this_year = datetime.datetime.now().year 56 # year_of_birth = random.randint( 57 # this_year-120, this_year 58 # ) 59 year_of_birth = get_random_year_of_birth()the test is still green.
I remove the commented lines
52 def test_factory_w_optional_arguments(self): 53 first_name = get_random_name() 54 year_of_birth = get_random_year_of_birth() 55 56 reality = src.person.factory( 57 first_name=first_name, 58 year_of_birth=year_of_birth, 59 ) 60 my_expectation = dict( 61 first_name=first_name, 62 last_name='doe', 63 sex='M', 64 age=calculate_age(year_of_birth), 65 ) 66 self.assertEqual(reality, my_expectation) 67 68 def test_factory_person_says_hello(self):I use the
get_random_year_of_birthfunction to replace the repetition of making the values for thethis_yearandyear_of_birthin test_factory_person_says_hello68 def test_factory_person_says_hello(self): 69 first_name = get_random_name() 70 last_name = get_random_name() 71 sex = pick_one('F', 'M') 72 # this_year = datetime.datetime.now().year 73 # year_of_birth = random.randint( 74 # this_year-120, this_year 75 # ) 76 year_of_birth = get_random_year_of_birth()the test is still green.
I remove the commented lines
67 def test_factory_person_says_hello(self): 68 first_name = get_random_name() 69 last_name = get_random_name() 70 sex = pick_one('F', 'M') 71 year_of_birth = get_random_year_of_birth() 72 73 a_random_person = src.person.factory( 74 first_name=first_name, 75 last_name=last_name, 76 sex=sex, 77 year_of_birth=year_of_birth, 78 ) 79 80 reality = src.person.say_hello(a_random_person) 81 my_expectation = ( 82 f'Hi, my name is {first_name} {last_name}' 83 f' and I am {calculate_age(year_of_birth)}' 84 ) 85 self.assertEqual(reality, my_expectation) 86 87 88# Exceptions seen 89# AssertionError 90# NameError 91# AttributeError 92# TypeError 93# SyntaxErrorI add a git commit message in the other terminal
git commit -am \ 'extract get_random_year_of_birth function'the terminal shows a summary of the changes then goes back to the command line.
test_update_factory_person_year_of_birth
I made a person named john in test_factory_person_says_hello with a year of birth of 1580, which means he is too old to be alive. That age is also outside the range I used for the random age, the oldest person the test makes is 120.
Maybe I made a mistake when typing his age and typed 5 instead of 9. How would I change the year of birth of a person made with the factory function if I cannot change the original year of birth?
I could try updating the
year_of_birthI could try making a function that takes a person and a new year of birth as inputs, and returns the person with the corrected age
I could make a new factory function that returns a dictionary with
year_of_birthas a key which allows me to change it, then make another function that calculates the age from the returned dictionary - this sounds like a lot of work, I would also have to rewrite the tests. No, thank you.
RED: make it fail
I add a new test for
100 for person in (joe, jane, john):
101 with self.subTest(name=person.first_name):
102 self.assertEqual(
103 person.hello(),
104 (
105 f'Hi, my name is {person.first_name} '
106 f'{person.last_name} '
107 f'and I am {person.get_age()}'
108 )
109 )
110
111 def test_update_factory_person_year_of_birth(self):
112 person = src.person.factory(
113 first_name='john',
114 last_name='smith',
115 year_of_birth=1580,
116 )
117 self.assertEqual(person.get('age'), 0)
118
119
120 # Exceptions seen
the terminal is my friend, and shows AssertionError
AssertionError: 446 != 0
GREEN: make it passs
I change the expectation
117 self.assertEqual(person.get('age'), 446)
the test passes.
REFACTOR: make it better
I try to use the
year_of_birthkey117 self.assertEqual(person.get('age'), 446) 118 119 person['year_of_birth'] 120 121 122# Exceptions seenthe terminal is my friend, and shows KeyError
KeyError: 'year_of_birth'there is no
year_of_birthkey in the dictionary returned by the factory functionI add KeyError to the list of Exceptions seen
122# Exceptions seen 123# AssertionError 124# NameError 125# AttributeError 126# TypeError 127# SyntaxError 128# KeyErrorI add assertRaises
117 self.assertEqual(person.get('age'), 446) 118 119 with self.assertRaises(KeyError): 120 person['year_of_birth'] 121 122 123# Exceptions seenthe test passes.
I add a new value for
year_of_birthwith the setdefault method119 with self.assertRaises(KeyError): 120 person['year_of_birth'] 121 self.assertEqual( 122 person.setdefault('year_of_birth', 1980), 123 None 124 ) 125 126 127# Exceptions seenthe terminal is my friend, and shows AssertionError
AssertionError: 1980 != NoneI change the expectation
121 self.assertEqual( 122 person.setdefault('year_of_birth', 1980), 123 1980 124 )the test passes because the dictionary now has a key named
year_of_birthwith the new valueI add an assertion for the age of
john smithagain121 self.assertEqual( 122 person.setdefault('year_of_birth', 1980), 123 1980 124 ) 125 self.assertEqual(person.get('age'), 46) 126 127# Exceptions seenthe terminal is my friend, and shows AssertionError
AssertionError: 446 != 46the factory function uses the value for
year_of_birthto calculate the age when it makes the dictionary.When I change the value or add the key, it does not do anything to the age. There has to be a better way
I change the expectation
125self.assertEqual(person.get('age'), 446)the test passes.
I can make a function that takes a person and a new year of birth as inputs, and returns the person with the correct age. I add an assertion
125 self.assertEqual(person.get('age'), 446) 126 127 self.assertEqual( 128 src.person.update_year_of_birth(person, 1980), 129 dict( 130 first_name=person.get('first_name'), 131 last_name=person.get('last_name'), 132 sex=person.get('sex'), 133 age=this_year()-1980, 134 ) 135 ) 136 137 138# Exceptions seenthe terminal is my friend, and shows AttributeError
AttributeError: module 'src.person' has no attribute 'update_year_of_birth'I add the function to
person.py16def say_hello(person): 17 return ( 18 f'Hi, my name is {person.get("first_name")} ' 19 f'{person.get("last_name")} ' 20 f'and I am {person.get("age")}' 21 ) 22 23 24def update_year_of_birth(): 25 return None 26 27 28class Person:the terminal is my friend, and shows TypeError
TypeError: update_year_of_birth() takes 0 positional arguments but 2 were givenI add the two names in parentheses
27def update_year_of_birth(person, new_year_of_birth): 28 return Nonethe terminal is my friend, and shows AssertionError
AssertionError: None != {'first_name': 'john', 'last_name': 'smith', 'sex': 'M', 'age': 46}I change the return statement
27def update_year_of_birth(person, new_year_of_birth): 28 return factory( 29 first_name=person.get('first_name'), 30 last_name=person.get('last_name'), 31 sex=person.get('sex'), 32 year_of_birth=new_year_of_birth, 33 ) 34 35 36class Person:the test passes.
I cannot update the year_of_birth key because the function returns a dictionary that does not have a year_of_birth key.
time to remove some repetition. I add a variable for the original year of birth in
test_update_factory_person_year_of_birthintest_person.py111 def test_update_factory_person_year_of_birth(self): 112 original_year_of_birth = 1580 113 114 person = src.person.factory(the test is still green.
I use the variable in the call to
src.person.factory112 original_year_of_birth = 1580 113 114 person = src.person.factory( 115 first_name='john', 116 last_name='smith', 117 # year_of_birth=1580, 118 year_of_birth=original_year_of_birth, 119 )still green.
I use the variable in the first assertion for the age
114 person = src.person.factory( 115 first_name='john', 116 last_name='smith', 117 # year_of_birth=1580, 118 year_of_birth=original_year_of_birth, 119 ) 120 # self.assertEqual(person.get('age'), 446) 121 self.assertEqual( 122 person.get('age'), 123 this_year()-original_year_of_birth 124 ) 125 126 with self.assertRaises(KeyError):green.
I use the variable in the second assertion for the age
126 with self.assertRaises(KeyError): 127 person['year_of_birth'] 128 self.assertEqual( 129 person.setdefault('year_of_birth', 1980), 130 1980 131 ) 132 # self.assertEqual(person.get('age'), 446) 133 self.assertEqual( 134 person.get('age'), 135 this_year()-original_year_of_birth 136 ) 137 138 self.assertEqual(still green.
I remove the commented lines
111 def test_update_factory_person_year_of_birth(self): 112 original_year_of_birth = 1580 113 114 person = src.person.factory( 115 first_name='john', 116 last_name='smith', 117 year_of_birth=original_year_of_birth, 118 ) 119 self.assertEqual( 120 person.get('age'), 121 this_year()-original_year_of_birth 122 ) 123 124 with self.assertRaises(KeyError): 125 person['year_of_birth'] 126 self.assertEqual( 127 person.setdefault('year_of_birth', 1980), 128 1980 129 ) 130 self.assertEqual( 131 person.get('age'), 132 this_year()-original_year_of_birth 133 ) 134 135 self.assertEqual( 136 src.person.update_year_of_birth(person, 1980), 137 dict( 138 first_name=person.get('first_name'), 139 last_name=person.get('last_name'), 140 sex=person.get('sex'), 141 age=this_year()-1980, 142 ) 143 ) 144 145 146# Exceptions seenthe test is still green.
I add a variable for the new year of birth
111 def test_update_factory_person_year_of_birth(self): 112 original_year_of_birth = 1580 113 new_year_of_birth = 1980 114 115 person = src.person.factory(still green.
I use the variable in the assertion for the call to the setdefault method
125 with self.assertRaises(KeyError): 126 person['year_of_birth'] 127 self.assertEqual( 128 # person.setdefault('year_of_birth', 1980), 129 person.setdefault('year_of_birth', new_year_of_birth), 130 # 1980 131 new_year_of_birth 132 ) 133 self.assertEqual(the test is still green.
I use the variable in the last assertion
138 self.assertEqual( 139 # src.person.update_year_of_birth(person, 1980), 140 src.person.update_year_of_birth( 141 person, 142 new_year_of_birth 143 ), 144 dict( 145 first_name=person.get('first_name'), 146 last_name=person.get('last_name'), 147 sex=person.get('sex'), 148 # age=this_year()-1980, 149 age=this_year()-new_year_of_birth, 150 ) 151 ) 152 153 154# Exceptions seenstill green.
I remove the commented lines
111 def test_update_factory_person_year_of_birth(self): 112 original_year_of_birth = 1580 113 new_year_of_birth = 1980 114 115 person = src.person.factory( 116 first_name='john', 117 last_name='smith', 118 year_of_birth=original_year_of_birth, 119 ) 120 self.assertEqual( 121 person.get('age'), 122 this_year()-original_year_of_birth 123 ) 124 125 with self.assertRaises(KeyError): 126 person['year_of_birth'] 127 self.assertEqual( 128 person.setdefault('year_of_birth', new_year_of_birth), 129 new_year_of_birth 130 ) 131 self.assertEqual( 132 person.get('age'), 133 this_year()-original_year_of_birth 134 ) 135 136 self.assertEqual( 137 src.person.update_year_of_birth( 138 person, 139 new_year_of_birth 140 ), 141 dict( 142 first_name=person.get('first_name'), 143 last_name=person.get('last_name'), 144 sex=person.get('sex'), 145 age=this_year()-new_year_of_birth, 146 ) 147 ) 148 149 150# Exceptions seengreen.
I add a variable for the original age calculation
111 def test_update_factory_person_year_of_birth(self): 112 original_year_of_birth = 1580 113 original_age = this_year() - original_year_of_birth 114 new_year_of_birth = 1980I use the variable in the first assertion for the age
121 person = src.person.factory( 122 first_name='john', 123 last_name='smith', 124 year_of_birth=original_year_of_birth, 125 ) 126 self.assertEqual( 127 person.get('age'), 128 # this_year()-original_year_of_birth 129 original_age 130 )the test is still green.
I use the variable in the second assertion for the age
127 with self.assertRaises(KeyError): 128 person['year_of_birth'] 129 self.assertEqual( 130 person.setdefault('year_of_birth', new_year_of_birth), 131 new_year_of_birth 132 ) 133 self.assertEqual( 134 person.get('age'), 135 # this_year()-original_year_of_birth 136 original_age 137 )still green.
I remove the commented lines
111 def test_update_factory_person_year_of_birth(self): 112 original_year_of_birth = 1580 113 original_age = this_year() - original_year_of_birth 114 new_year_of_birth = 1980 115 116 person = src.person.factory( 117 first_name='john', 118 last_name='smith', 119 year_of_birth=original_year_of_birth, 120 ) 121 self.assertEqual( 122 person.get('age'), 123 original_age 124 ) 125 126 with self.assertRaises(KeyError): 127 person['year_of_birth'] 128 self.assertEqual( 129 person.setdefault('year_of_birth', new_year_of_birth), 130 new_year_of_birth 131 ) 132 self.assertEqual( 133 person.get('age'), 134 original_age 135 ) 136 137 self.assertEqual( 138 src.person.update_year_of_birth( 139 person, 140 new_year_of_birth 141 ), 142 dict( 143 first_name=person.get('first_name'), 144 last_name=person.get('last_name'), 145 sex=person.get('sex'), 146 age=this_year()-new_year_of_birth, 147 ) 148 ) 149 150 151# Exceptions seen
I had to make a new person with the same first name, last name, sex and the new year of birth to change the year of birth. How would I solve this problem with a class?
test_person_tests
I want to write the solution without looking at the tests
RED: make it fail
I go back to the terminal that is running the tests
I close
test_person.pyin the editorthen I delete all the text in
person.py, the terminal is my friend, and shows AttributeErrorFAILED ...test_factory_person_says_hello - AttributeError: module 'src.person' has no attribute 'factory' FAILED ...test_factory_w_keyword_arguments - AttributeError: module 'src.person' has no attribute 'factory' FAILED ...test_factory_w_optional_arguments - AttributeError: module 'src.person' has no attribute 'factory'because there is nothing in
person.pywith the namefactory
GREEN: make it pass
I add the name
1factorythe terminal is my friend, and shows NameError
NameError: name 'factory' is not definedbecause I have not told Python what
factorymeansI point it to None to define it
1factory = Nonethe terminal is my friend, and shows TypeError
TypeError: 'NoneType' object is not callablebecause
factorypoints to None and I cannot call None like a functionI make
factorya function to make it callable1def factory(): 2 return Nonethe terminal is my friend, and shows TypeError
TypeError: factory() got an unexpected keyword argument 'first_name'because the test called the factory function with a keyword argument (
first_name) that is not in the function definitionI add
first_nameto the function definition1def factory(first_name): 2 return Nonethe terminal is my friend, and shows TypeError
TypeError: factory() got an unexpected keyword argument 'year_of_birth'because the test called the factory function with a keyword argument (
year_of_birth) that is not in the function definitionI add
year_of_birthto the function definition1def factory(first_name, year_of_birth): 2 return Nonethe terminal is my friend, and shows AssertionError
AssertionError: None != {'first_name': Y, 'last_name': 'doe', 'sex': 'M', 'age': X}because the assertion expects a dictionary and the factory function returns None
I copy and paste the dictionary from the terminal to make the function return a dictionary instead of None
1def factory(first_name, year_of_birth): 2 return { 3 'first_name': 'john', 4 'last_name': 'doe', 5 'sex': 'M', 6 'age': 55, 7 }the terminal is my friend, and shows AssertionError
AssertionError: {'first_name': A, 'last_name': 'doe', 'sex': 'M', 'age': Y} != {'first_name': Z, 'last_name': 'doe', 'sex': 'M', 'age': X}I use the
first_nameinput parameter in the return statement1def factory(first_name, year_of_birth): 2 return { 3 # 'first_name': 'john', 4 'first_name': first_name, 5 'last_name': 'doe', 6 'sex': 'M', 7 'age': 55, 8 }the first name matches and if the ages are different, the terminal is my friend, and shows AssertionError
AssertionError: {'first_name': Z, 'last_name': 'doe', 'sex': 'M', 'age': Y} != {'first_name': Z, 'last_name': 'doe', 'sex': 'M', 'age': X}and TypeError
TypeError: factory() got an unexpected keyword argument 'last_name'. Did you mean 'first_name'?I use the
year_of_birthinput parameter in the return statement for the value ofage1def factory(first_name, year_of_birth): 2 return { 3 # 'first_name': 'john', 4 'first_name': first_name, 5 'last_name': 'doe', 6 'sex': 'M', 7 # 'age': 55, 8 'age': year_of_birth, 9 }the terminal is my friend, and shows AssertionError
AssertionError: {'first_name': Y, 'last_name': 'doe', 'sex': 'M', 'age': ABCD} != {'first_name': Y, 'last_name': 'doe', 'sex': 'M', 'age': X}because the factory function returned 4 digits (a year) as the value for the
agekey, and the assertion expects the difference between that value and the current yearI add an import statement for the datetime module at the top of the file
1import datetime 2 3 4def factory(first_name, year_of_birth):the terminal still shows AssertionError
I use the datetime module to get the current year for the
agecalculation4def factory(first_name, year_of_birth): 5 return { 6 # 'first_name': 'john', 7 'first_name': first_name, 8 'last_name': 'doe', 9 'sex': 'M', 10 # 'age': 55, 11 # 'age': year_of_birth, 12 'age': ( 13 datetime.datetime.today().year 14 -year_of_birth 15 ), 16 }the terminal is my friend, and shows TypeError
TypeError: factory() got an unexpected keyword argument 'last_name'. Did you mean 'first_name'?because the test called the factory function with a keyword argument (
last_name) that is not in the function definitionI add a new input parameter to the function
4def factory( 5 first_name, year_of_birth, 6 last_name, 7 ):the terminal is my friend, and shows TypeError
TypeError: factory() missing 1 required positional argument: 'last_name'because the test called the function with another argument and Python took that argument as a positional argument for
last_nameI add a default value for
last_nameso Python does not take it is a positional argument when a name is not given4def factory( 5 first_name, year_of_birth, 6 last_name=None, 7 ):the terminal is my friend, and shows TypeError
TypeError: factory() got an unexpected keyword argument 'sex'because the test called the factory function with a keyword argument (
sex) that is not in the function definitionI add the name to the definition of the function
4def factory( 5 first_name, year_of_birth, 6 last_name=None, sex, 7 ):the terminal is my friend, and shows SyntaxError
SyntaxError: parameter without a default follows parameter with a defaultbecause parameters without default values must come before parameters with default values
I add a default value for
sex1def factory( 2 first_name, year_of_birth, 3 last_name=None, sex=None, 4 ):the terminal is my friend, and shows AssertionError
first_nameandagematch. If the last names are different, the terminal is my friend, and shows AssertionErrorAssertionError: {'first_name': C, 'last_name': B, 'sex': Z, 'age': X} != {'first_name': C, 'last_name': A, 'sex': Y, 'age': X}and AttributeError
AttributeError: module 'src.person' has no attribute 'say_hello'I use the
sexinput parameter in the return statement4def factory( 5 first_name, year_of_birth, 6 last_name=None, sex=None, 7 ): 8 return { 9 # 'first_name': 'john', 10 'first_name': first_name, 11 'last_name': 'doe', 12 # 'sex': 'M', 13 'sex': sex, 14 # 'age': 55, 15 # 'age': year_of_birth, 16 'age': ( 17 datetime.datetime.today().year 18 -year_of_birth 19 ), 20 }the terminal is my friend, and shows AssertionError
AssertionError: {'first_name': Y, 'last_name': 'doe', 'sex': None, 'age': X} != {'first_name': Y, 'last_name': 'doe', 'sex': 'M', 'age': X}because the assertion expects
'M'as the value ofsexand the function returns None which is its default valueI change the default value of
sexto'M'4def factory( 5 first_name, year_of_birth, 6 last_name=None, sex='M', 7 ):I use ctrl+s (Windows/Linux) or command+s (MacOS) to run the test a few times and if the
last_nameis not'doe', the terminal is my friend, and shows AssertionErrorAssertionError: {'first_name': A, 'last_name': 'doe', 'sex': Y, 'age': X} != {'first_name': A, 'last_name': Z, 'sex': Y, 'age': X}because the
last_namevalue is different between the two dictionaries. It still shows AttributeErrorAttributeError: module 'src.person' has no attribute 'say_hello'I use the
last_nameinput parameter in the return statement4def factory( 5 first_name, year_of_birth, 6 last_name=None, sex='M', 7 ): 8 return { 9 # 'first_name': 'john', 10 'first_name': first_name, 11 # 'last_name': 'doe', 12 'last_name': last_name, 13 # 'sex': 'M', 14 'sex': sex, 15 # 'age': 55, 16 # 'age': year_of_birth, 17 'age': ( 18 datetime.datetime.today().year 19 -year_of_birth 20 ), 21 }the terminal is my friend, and shows AssertionError
AssertionError: {'first_name': Z, 'last_name': None, 'sex': Y, 'age': X} != {'first_name': Z, 'last_name': 'doe', 'sex': Y, 'age': X}because the assertion expects
'doe'as the value oflast_nameand the function returns None which is its default valueI change the default value for
last_nameto match the expectation1def factory( 2 first_name, year_of_birth, 3 last_name='doe', sex='M', 4 ):the terminal is my friend, and shows AttributeError
AttributeError: module 'src.person' has no attribute 'say_hello'because I do not have a definition for
say_helloI add
say_hello1import datetime 2 3 4say_hello 5 6 7def factory( 8 first_name, year_of_birth, 9 last_name='doe', sex='M', 10 ):the terminal is my friend, and shows NameError
NameError: name 'say_hello' is not definedI point it to None to define it
1import datetime 2 3 4say_hello = None 5 6 7def factory( 8 first_name, year_of_birth, 9 last_name='doe', sex='M', 10 ):the terminal is my friend, and shows TypeError
TypeError: 'NoneType' object is not callablebecause
say_hellopoints to None and I cannot call None like a functionI make
say_helloa function to make it callable1import datetime 2 3 4def say_hello(): 5 return None 6 7 8def factory( 9 first_name, year_of_birth, 10 last_name='doe', sex='M', 11 ):the terminal is my friend, and shows TypeError
TypeError: say_hello() takes 0 positional arguments but 1 was givenbecause the function definition for
say_hellodoes not allow calling it with inputs (the parentheses are empty) and the test sends input.I add a name to the function definition
5def say_hello(argument): 6 return Nonethe terminal is my friend, and shows AssertionError
AssertionError: None != 'Hi, my name is Z Y and I am Z'because the test expects a string and the
say_hellofunction returns NoneI copy the string from the terminal and paste it to replace the return statement
4def say_hello(argument): 5 return 'Hi, my name is jane doe and I am 66'the terminal is my friend, and shows AssertionError
AssertionError: 'Hi, my name is Z Y and I am X' != 'Hi, my name is A B and I am C'I use ctrl+s (Windows/Linux) or command+s (MacOS) to run the test a few times and the names and age change
I return the input to compare it with what the test expects
4def say_hello(argument): 5 return argument 6 return 'Hi, my name is jane doe and I am 66'the terminal shows AssertionError
the test sends a dictionary as input and expects a string as output, and the string uses the values of the
first_name,last_nameandagekeys in itI return an f-string with the values of the
first_name,last_nameandagekeys from the dictionary4def say_hello(argument): 5 return ( 6 f'Hi, my name is {argument.get("first_name")}' 7 f' {argument.get("last_name")}' 8 f' and I am {argument.get("age")}' 9 ) 10 return argument 11 return 'Hi, my name is jane doe and I am 66'the test passes. Okay!
REFACTOR: make it better
I use the
Rename Symbolfeature of the Integrated Development Environment (IDE) to changeargumentto make it clearer4def say_hello(a_dictionary): 5 return ( 6 f'Hi, my name is {a_dictionary.get("first_name")}' 7 f' {a_dictionary.get("last_name")}' 8 f' and I am {a_dictionary.get("age")}' 9 ) 10 return a_dictionary 11 return 'Hi, my name is jane doe and I am 66'I remove the other return statements
1import datetime 2 3 4def say_hello(a_dictionary): 5 return ( 6 f'Hi, my name is {a_dictionary.get("first_name")}' 7 f' {a_dictionary.get("last_name")}' 8 f' and I am {a_dictionary.get("age")}' 9 ) 10 11 12def factory( 13 first_name, year_of_birth, 14 last_name='doe', sex='M', 15 ):I remove the commented lines from the factory function
12def factory( 13 first_name, year_of_birth, 14 last_name='doe', sex='M', 15 ): 16 return { 17 'first_name': first_name, 18 'last_name': last_name, 19 'sex': sex, 20 'age': ( 21 datetime.datetime.today().year 22 -year_of_birth 23 ), 24 }This factory function only has two parameters with default values (
last_nameandsex)def factory( first_name, year_of_birth, last_name='doe', sex='M', ):the first solution had three parameters with default values (
last_name,sexandyear_of_birth)def factory( first_name, last_name='doe', sex='M', year_of_birth=None, ):I can learn new things from repetition.
I add a git commit message in the other terminal
git commit -am 'refactor factory and say_hello'the terminal shows a summary of the changes then goes back to the command line.
I go back to the terminal that is running the tests
close the project
I close
person.pyin the editorI click in the terminal where the tests are running, then use q on the keyboard to leave the tests. The terminal goes back to the command line.
I change directory to the parent of
personcd ..the terminal shows
...\pumping_pythonI am back in the
pumping_pythondirectory
review
I ran tests to make
a function that takes in keyword arguments as input, has default values for some of them, performs an action based on an input and returns a dictionary as output
a function that takes in a dictionary and returns a string as output with values of keys from the dictionary
I also saw these Exceptions
code from the chapter
what is next?
you know:
rate pumping python
If this has been a 7 star experience for you, please CLICK HERE to leave a 5 star review of pumping python. It helps other people get into the book too.